Министерство науки и высшего образования Российской Федерации
Федеральное государственное бюджетное образовательное учреждение
высшего образования «Петрозаводский государственный университет»
Институт математики и информационных технологий
кафедра информатики и математического обеспечения
Агеев Егор Юрьевич
РАЗРАБОТКА СИСТЕМЫ УПРАВЛЕНИЯ ЛИЧНЫМИ
ФИНАНСАМИ
ВЫПУСКНАЯ КВАЛИФИКАЦИОННАЯ РАБОТА
Направление подготовки бакалавра
09.03.02 Информационные системы и технологии
Форма обучения - заочная
Допущена к защите.
Заведующий кафедрой, Руководитель
к. т. н., доцент к. т. н., доцент
Богоявленский Ю. А. Богоявленский Ю. А.
« » 202 г. « » 202 г.
ст. преподаватель
Чистяков Д. Б.
« » 202 г.
Петрозаводск — 2026
Оглавление
Введение 4
1 Анализ предметной области и выбор технологий 8
1.1 Анализ предметной области . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.1.1 Управление личными финансами . . . . . . . . . . . . . . . . . . . . . 8
1.1.2 Проблема вовлечённости пользователей . . . . . . . . . . . . . . . . . 9
1.1.3 Геймификация как инструмент повышения вовлечённости . . . . . . . 9
1.2 Обзор существующих аналогов . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.2.1 Monefy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.2.2 YNAB (You Need A Budget) . . . . . . . . . . . . . . . . . . . . . . . . 12
1.2.3 CoinKeeper . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.2.4 Дзен-мани . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.2.5 Сравнительный анализ . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
1.3 Обзор и обоснование выбора технологий . . . . . . . . . . . . . . . . . . . . . 17
1.3.1 Платформа: Telegram Mini Apps . . . . . . . . . . . . . . . . . . . . . . 17
1.3.2 Frontend: выбор технологий . . . . . . . . . . . . . . . . . . . . . . . . 18
1.3.3 Backend: выбор технологий . . . . . . . . . . . . . . . . . . . . . . . . . 19
1.3.4 Инфраструктура . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
1.4 Требования к системе . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
1.4.1 Функциональные требования . . . . . . . . . . . . . . . . . . . . . . . . 21
1.4.2 Нефункциональные требования . . . . . . . . . . . . . . . . . . . . . . 22
1.5 Постановка задачи . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2 Проектирование архитектуры и моделирование системы 24
2.1 Общая архитектура системы . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2.1.1 Архитектурный стиль и компоненты . . . . . . . . . . . . . . . . . . . 24
2.1.2 Диаграмма компонентов . . . . . . . . . . . . . . . . . . . . . . . . . . 25
2.1.3 Инфраструктурная схема . . . . . . . . . . . . . . . . . . . . . . . . . . 25
2.2 Проектирование базы данных . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
2.2.1 Обзор схемы данных . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
2.2.2 Описание сущностей . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
2.2.3 ER-диаграмма . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
2.2.4 Ключевые связи и ограничения . . . . . . . . . . . . . . . . . . . . . . 28
2.3 Проектирование REST API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
2.3.1 Модульная структура NestJS . . . . . . . . . . . . . . . . . . . . . . . . 28
2.3.2 Таблица эндпоинтов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
2.3.3 Формат запросов и ответов . . . . . . . . . . . . . . . . . . . . . . . . . 29
2.4 Проектирование системы аутентификации . . . . . . . . . . . . . . . . . . . . 29
2.4.1 Общая схема авторизации . . . . . . . . . . . . . . . . . . . . . . . . . 29
2.4.2 Диаграмма последовательности . . . . . . . . . . . . . . . . . . . . . . 30
2
2.4.3 Верификация HMAC-SHA256 . . . . . . . . . . . . . . . . . . . . . . . 30
2.4.4 JWT-токен . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
2.5 Проектирование пользовательского интерфейса . . . . . . . . . . . . . . . . . 31
2.5.1 Навигационная структура приложения . . . . . . . . . . . . . . . . . . 31
2.5.2 Дизайн-система . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
2.6 Проектирование системы геймификации . . . . . . . . . . . . . . . . . . . . . 35
2.6.1 XP-система и уровни . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
2.6.2 Система стрика . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
2.6.3 Система челленджей . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
2.6.4 Таблица лидеров . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
3 Особенности реализации и тестирование 40
3.1 Реализация серверной части . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
3.1.1 Структура проекта NestJS . . . . . . . . . . . . . . . . . . . . . . . . . 40
3.1.2 PrismaService: паттерн композиции . . . . . . . . . . . . . . . . . . . . 41
3.1.3 Модуль авторизации . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
3.1.4 Модуль транзакций и начисление XP . . . . . . . . . . . . . . . . . . . 43
3.1.5 Модуль повторяющихся транзакций . . . . . . . . . . . . . . . . . . . . 44
3.1.6 Модуль финансовых пространств . . . . . . . . . . . . . . . . . . . . . 45
3.1.7 Модуль целей накопления . . . . . . . . . . . . . . . . . . . . . . . . . 46
3.1.8 Модуль челленджей . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
3.1.9 Модуль аналитики . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
3.1.10 Модуль таблицы лидеров . . . . . . . . . . . . . . . . . . . . . . . . . . 49
3.2 Реализация клиентской части . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
3.2.1 Структура React-проекта . . . . . . . . . . . . . . . . . . . . . . . . . . 50
3.2.2 Разделение состояния: Zustand и TanStack Query . . . . . . . . . . . . 51
3.2.3 Интеграция с Telegram Mini App SDK . . . . . . . . . . . . . . . . . . 52
3.2.4 Темизация приложения . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
3.2.5 Онбординг и оптимистичное обновление . . . . . . . . . . . . . . . . . 53
3.2.6 Обход ограничений скролла Telegram . . . . . . . . . . . . . . . . . . . 54
3.3 Инфраструктура и деплой . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
3.3.1 Конфигурация Nginx . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
3.3.2 PM2: менеджер процессов . . . . . . . . . . . . . . . . . . . . . . . . . 56
3.3.3 Docker Compose для PostgreSQL . . . . . . . . . . . . . . . . . . . . . . 56
3.3.4 Cloudflare Tunnel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
3.4 Тестирование . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
3.4.1 Стратегия тестирования . . . . . . . . . . . . . . . . . . . . . . . . . . 57
3.4.2 Тестовое окружение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
3.4.3 Тестирование API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
3.4.4 Функциональное тестирование . . . . . . . . . . . . . . . . . . . . . . . 59
3.4.5 Пользовательское приёмочное тестирование . . . . . . . . . . . . . . . 59
3.4.6 Анализ результатов тестирования . . . . . . . . . . . . . . . . . . . . . 64
3.4.7 Выявленные проблемы и решения . . . . . . . . . . . . . . . . . . . . . 64
Заключение 66
Библиографический список использованной литературы 68
Введение
Актуальность темы. В условиях современной экономики умение управ-
лять личными финансами становится одним из ключевых навыков, определя-
ющих качество жизни человека. Контроль над доходами и расходами, форми-
рование сбережений и планирование бюджета позволяют не только избежать
долговой нагрузки, но и достигать долгосрочных финансовых целей. Вместе
с тем, согласно данным Национального агентства финансовых исследований,
более 60% граждан России не ведут систематического учёта своих финансов,
а среди молодёжи в возрасте 18–25 лет этот показатель ещё выше [2].
Рынок мобильных приложений для управления личными финансами ак-
тивно развивается: по данным аналитической компании Statista, в 2024 го-
ду мировой объём этого сегмента превысил 1,5 млрд долларов. Тем не ме-
нее, несмотря на широкое разнообразие предложений, большинство подобных
приложений сталкиваются с фундаментальной проблемой удержания пользо-
вателей. Исследования показывают, что около 80% пользователей прекраща-
ют регулярно пользоваться финансовыми приложениями в течение первых
трёх недель после установки [2]. Основная причина – монотонность: рутин-
ный ввод данных воспринимается как обязанность, а не как осознанная при-
вычка.
Одним из перспективных подходов к решению данной проблемы является
геймификация – внедрение игровых механик в неигровые контексты с целью
повышения мотивации и вовлечённости пользователей [5]. Применение таких
элементов, как системы очков опыта, уровней, ежедневных заданий и таблиц
рейтинга, успешно зарекомендовало себя в образовательных, фитнес- и про-
дуктивных приложениях. Перенос этих механик в сферу личных финансов
открывает возможность формирования устойчивой привычки к финансовому
учёту через позитивное подкрепление.
4
Дополнительным фактором, определяющим актуальность настоящей ра-
боты, является стремительный рост аудитории мессенджера Telegram и по-
явление технологии Telegram Mini Apps – встроенных веб-приложений, за-
пускаемых непосредственно внутри мессенджера без установки отдельного
программного обеспечения [14]. По состоянию на 2024 год ежемесячная ауди-
тория Telegram превысила 900 млн пользователей, что делает эту платформу
привлекательной средой для разработки массовых пользовательских прило-
жений. Встроенная система авторизации через аккаунт Telegram существенно
снижает порог входа для новых пользователей.
Таким образом, разработка геймифицированной системы управления лич-
ными финансами в формате Telegram Mini App является актуальной как с
практической, так и с технической точки зрения.
Объект исследования – система управления личными финансами с эле-
ментами геймификации.
Предмет исследования – методы и технологии разработки клиент-
серверных веб-приложений для платформы Telegram Mini Apps, а также ме-
ханизмы геймификации, применяемые для повышения вовлечённости поль-
зователей.
Цель работы – разработка системы управления личными финансами в
виде Telegram Mini App с элементами геймификации, обеспечивающей дол-
госрочное вовлечение пользователей в контроль над личным бюджетом.
Для достижения поставленной цели необходимо решить следу-
ющие задачи:
1. Провести анализ предметной области, изучить существующие аналоги
систем управления личными финансами и выявить их ключевые недо-
статки.
2. Обосновать выбор технологий и архитектурных решений для реализации
клиентской и серверной частей приложения.
3. Спроектировать архитектуру системы, включая схему базы данных,
структуру REST API, систему аутентификации и механизмы геймифи-
кации.
5
4. Реализовать клиентскую часть приложения в виде Telegram Mini App с
поддержкой учёта финансов, совместных бюджетов и игровых механик.
5. Реализовать серверную часть приложения, обеспечивающую хранение и
обработку данных, авторизацию пользователей и бизнес-логику гейми-
фикации.
6. Выполнить развёртывание системы на сервере и провести функциональ-
ное тестирование.
Методы исследования. В работе использованы методы сравнительно-
го анализа при обзоре аналогов, объектно-ориентированного и компонент-
ного проектирования при разработке архитектуры системы, а также методы
функционального тестирования при проверке работоспособности реализован-
ной системы.
Практическая значимость. Результатом работы является полнофунк-
циональное приложение FinQuest, доступное пользователям через мессен-
джер Telegram. Приложение позволяет вести учёт доходов и расходов, управ-
лять несколькими финансовыми пространствами совместно с другими поль-
зователями, ставить цели накопления и получать мотивацию к регулярно-
му финансовому учёту через игровые механики. Разработанные архитектур-
ные и технические решения могут быть использованы при создании других
клиент-серверных приложений на платформе Telegram Mini Apps.
Структура работы. Данная работа состоит из введения, трёх глав, за-
ключения и библиографического списка.
В первой главе проводится анализ предметной области управления лич-
ными финансами, рассматриваются существующие аналоги и их недостатки,
обосновывается выбор технологий, а также формулируются функциональные
и нефункциональные требования к разрабатываемой системе.
Во второй главе описывается проектирование системы: общая архитек-
тура, схема базы данных, структура REST API, система аутентификации,
пользовательский интерфейс и механизмы геймификации.
6
В третьей главе рассматриваются особенности реализации серверной и
клиентской частей приложения, описывается процесс развёртывания системы
на сервере, а также приводятся результаты тестирования.
В заключении подводятся итоги выполненной работы, формулируются ос-
новные результаты и намечаются перспективы дальнейшего развития систе-
мы.
7
Глава 1
Анализ предметной области и выбор
технологий
1.1 Анализ предметной области
1.1.1 Управление личными финансами
Управление личными финансами – это систематический процесс плани-
рования, учёта, контроля и анализа денежных потоков физического лица
с целью достижения финансовой стабильности и поставленных финансовых
целей. Данная область охватывает такие аспекты, как бюджетирование, фор-
мирование сбережений, управление долгами и инвестиции.
Ключевым элементом управления личными финансами является бюдже-
тирование – процесс планирования доходов и расходов на определённый пе-
риод. Регулярное ведение бюджета позволяет человеку понять структуру сво-
их трат, выявить нецелевые расходы и высвободить средства для сбережений.
Среди наиболее распространённых методов бюджетирования выделяют:
• Метод «50/30/20» – 50% доходов направляется на обязательные рас-
ходы, 30% – на желания, 20% – на сбережения и погашение долгов.
• Конвертный метод – денежные средства физически или виртуально
распределяются по категориям расходов в начале периода.
• Метод нулевого бюджета – каждый рубль дохода заранее получает
назначение, сумма всех статей равна доходу (методология YNAB).
Вне зависимости от выбранного метода, систематический учёт финансов
8
требует от пользователя регулярных действий – фиксации каждой транзак-
ции. Именно здесь возникает главная проблема: сформировать и поддержи-
вать эту привычку крайне сложно.
1.1.2 Проблема вовлечённости пользователей
Исследования в области поведенческих финансов и психологии привычек
демонстрируют устойчивую закономерность: большинство пользователей, на-
чавших вести финансовый учёт, прекращают делать это в течение первых
трёх недель. По данным исследований финансового поведения [2], около 80%
загруженных финансовых приложений перестают использоваться в течение
первого месяца.
Основные причины отказа от использования финансовых приложений:
1. Монотонность. Ежедневный ввод транзакций воспринимается как ме-
ханическая рутина, не несущая немедленной ценности.
2. Отсутствие обратной связи. Пользователь не получает позитивного
подкрепления за регулярное ведение учёта – приложение не «замечает»
его усилий.
3. Сложность освоения. Часть приложений ориентирована на продви-
нутых пользователей и требует знания финансовых концепций (YNAB).
4. Отсутствие социального измерения. Финансовый учёт воспринима-
ется как сугубо личное, изолированное занятие.
1.1.3 Геймификация как инструмент повышения вовлечённости
Геймификация – это применение игровых элементов и механик в неигро-
вых контекстах с целью повышения мотивации, вовлечённости и лояльности
пользователей [5]. Термин был введён в широкий оборот в 2010 году и с тех
пор нашёл применение в образовании, фитнесе, корпоративном обучении и
потребительских приложениях.
Теоретической основой геймификации служит теория самодетерминации
Деси и Райана [4], согласно которой устойчивая мотивация формируется при
9
удовлетворении трёх базовых психологических потребностей: компетентно-
сти, автономии и социальной связанности. Игровые механики адресуют каж-
дую из них:
• Компетентность реализуется через системы уровней и очков опыта –
пользователь видит прогресс и ощущает развитие.
• Автономия поддерживается через челленджи и задания, которые поль-
зователь выбирает самостоятельно.
• Социальная связанность обеспечивается таблицами лидеров и сов-
местными пространствами.
Мета-анализ эмпирических исследований, проведённый Хамари и соавто-
рами [6], показал, что геймификация оказывает статистически значимый по-
ложительный эффект на вовлечённость пользователей в 24 из 30 изученных
случаев. Более поздний систематический обзор более 270 исследований [8]
подтвердил этот вывод и зафиксировал устойчивый рост числа работ, эм-
пирически доказывающих эффективность геймификации. Применительно к
финансовым приложениям геймификация позволяет сформировать устойчи-
вую привычку к ежедневному ведению учёта через систему позитивных под-
креплений.
1.2 Обзор существующих аналогов
Для анализа были выбраны четыре наиболее популярных и показатель-
ных представителя рынка приложений для управления личными финансами,
охватывающих как зарубежный, так и российский сегмент.
1.2.1 Monefy
Monefy [9] – мобильное приложение для быстрого учёта расходов, раз-
работанное компанией Taction Software. Ключевая особенность – минимали-
стичный интерфейс на основе круговой диаграммы, позволяющий добавить
расход в два касания.
Основные возможности:
10
• быстрое добавление расходов и доходов с выбором категории;
• несколько кошельков (наличные, карта и др.);
• базовая аналитика в виде круговой диаграммы по категориям;
• экспорт данных в CSV;
• синхронизация через Dropbox или Google Drive (платная функция).
Недостатки:
• отсутствие целей накопления и совместного бюджета;
• нет механизмов мотивации к регулярному ведению учёта;
• ограниченная аналитика в бесплатной версии;
• не интегрировано с популярными мессенджерами.
11
Рис. 1.1: Интерфейс приложения Monefy
1.2.2 YNAB (You Need A Budget)
YNAB [15] – комплексная система бюджетирования, реализующая автор-
скую методологию «каждому доллару – задачу» (zero-based budgeting). При-
ложение ориентировано на осознанное планирование: пользователь вручную
распределяет каждый рубль дохода по категориям расходов ещё до того, как
потратит его.
Основные возможности:
• детальное бюджетирование по категориям с планированием на месяц
вперёд;
12
• автоматический импорт транзакций из банков (для ряда стран);
• расширенная аналитика: отчёты по net worth, динамика расходов, про-
гнозы;
• обучающие материалы и вебинары по финансовой грамотности;
• совместный доступ к бюджету для пары или семьи.
Недостатки:
• высокая стоимость подписки – около 14 $ в месяц или 99 $ в год;
• высокий порог входа: методология требует времени на изучение;
• отсутствие геймификации – нет механизмов мотивации;
• отсутствует интеграция с Telegram.
Рис. 1.2: Интерфейс приложения YNAB
1.2.3 CoinKeeper
CoinKeeper [3] – российское приложение для учёта финансов с визуальным
интерфейсом, построенным на метафоре монет и кошельков. Пользователь
перетаскивает монеты из источника дохода в категорию расхода, что делает
ввод данных наглядным и интуитивным.
Основные возможности:
• несколько счетов с разными валютами;
• цели накопления с отображением прогресса;
• повторяющиеся платежи и напоминания;
13
• отчёты и графики по периодам;
• импорт выписок из российских банков (Сбербанк, Тинькофф и др.).
Недостатки:
• большинство функций доступны только в платной версии (подписка от
299 руб./мес.);
• отсутствие совместного бюджета для нескольких пользователей;
• нет игровых механик;
• не интегрировано с Telegram.
Рис. 1.3: Интерфейс приложения CoinKeeper
1.2.4 Дзен-мани
Дзен-мани [1] – российское приложение для автоматического учёта финан-
сов, ориентированное прежде всего на интеграцию с банками. Приложение
автоматически подтягивает транзакции из подключённых банковских акка-
унтов, минимизируя ручной ввод данных.
Основные возможности:
• автоматический импорт транзакций из более чем 50 российских банков;
14
• совместный бюджет для пары или семьи;
• цели накопления и долги;
• планирование бюджета по категориям;
• расширенная аналитика с прогнозами.
Недостатки:
• для автоматического импорта требуется предоставить доступ к банков-
ским данным, что вызывает опасения у части пользователей;
• отсутствие геймификации;
• нет интеграции с Telegram;
• полный функционал доступен по подписке.
15
Рис. 1.4: Интерфейс приложения Дзен-мани
1.2.5 Сравнительный анализ
По результатам анализа аналогов составлена сравнительная таблица 1.1,
охватывающая ключевые характеристики рассмотренных систем.
Анализ показывает, что ни один из рассмотренных продуктов не реали-
зует геймификацию как инструмент удержания пользователей, и ни один не
работает в формате Telegram Mini App. Разрабатываемая система FinQuest
закрывает оба эти пробела, предлагая бесплатный, мотивирующий и легко-
доступный инструмент управления личными финансами.
16
Таблица 1.1: Сравнение аналогов систем управления личными финансами
Критерий Monefy YNAB CoinKeeper Дзен-мани FinQuest
Учёт доходов и + + + + +
расходов
Совместный бюд- – + – + +
жет
Цели накопления – + + + +
Повторяющиеся – + + + +
платежи
Аналитика базовая расширенная средняя расширенная средняя
Геймификация – – – – +
Telegram Mini App – – – – +
Бесплатный до- + – частично частично +
ступ
1.3 Обзор и обоснование выбора технологий
1.3.1 Платформа: Telegram Mini Apps
Telegram Mini Apps [14] – технология встроенных веб-приложений, запус-
каемых непосредственно в мессенджере Telegram. Технически Mini App пред-
ставляет собой обычное веб-приложение (HTML, CSS, JavaScript), которое
открывается во встроенном браузере Telegram и взаимодействует с ним через
JavaScript SDK.
Преимущества Telegram Mini Apps для данного проекта:
• Нулевой порог входа. Пользователь открывает приложение одним на-
жатием в Telegram – установка не требуется.
• Встроенная авторизация. Telegram передаёт в приложение верифи-
цированные данные аккаунта (initData), полностью исключая необхо-
димость отдельной регистрации.
• Широкий охват. Ежемесячная аудитория Telegram превышает 900 млн
пользователей.
• Нативные UI-элементы. SDK предоставляет доступ к кнопке «На-
зад», всплывающим диалогам, haptic-откликам и тематическим цветам
Telegram.
• Кроссплатформенность. Одна кодовая база работает на iOS, Android
и десктопе.
17
Альтернативой могла бы служить разработка нативного мобильного при-
ложения (iOS/Android) или прогрессивного веб-приложения (PWA). Однако
нативная разработка требует поддержки двух кодовых баз и прохождения
ревью в App Store и Google Play, что существенно замедляет итерации. PWA
лишён встроенного канала дистрибуции и механизма авторизации. Telegram
Mini App сочетает простоту веб-разработки с широким охватом и встроенной
идентификацией пользователя.
1.3.2 Frontend: выбор технологий
Фреймворк: React 19
При выборе UI-фреймворка рассматривались три основных варианта:
React, Vue и Svelte. Сравнение приведено в таблице 1.2.
Таблица 1.2: Сравнение frontend-фреймворков
Критерий React Vue Svelte
Размер экосистемы очень большая большая средняя
TypeScript-поддержка отличная хорошая хорошая
Совместимость с TanStack Query отличная хорошая средняя
Сообщество и документация очень зрелые зрелые развивающееся
Производительность высокая высокая очень высокая
React [13] выбран ввиду наибольшей зрелости экосистемы, широкой под-
держки TypeScript и наилучшей совместимости с выбранными библиотеками
(TanStack Query, Zustand, React Router).
Сборщик: Vite
Vite выбран как инструмент сборки благодаря мгновенному старту dev-
сервера (через ES Modules) и быстрой сборке для продакшена. По сравнению
с Create React App (Webpack) Vite значительно быстрее как в режиме разра-
ботки, так и при финальной сборке.
Стилизация: Tailwind CSS v4
Tailwind – утилитарный CSS-фреймворк, позволяющий описывать стили
прямо в JSX-разметке через классы. Это ускоряет разработку и исключает
необходимость поддерживать отдельные CSS-файлы. Четвёртая версия обес-
печивает нативную поддержку CSS-переменных, что критично для реализа-
ции системы тем оформления.
18
Управление состоянием: Zustand + TanStack Query v5
Для управления состоянием применяется двухуровневый подход: Zustand
отвечает за глобальное клиентское состояние (данные пользователя, активное
пространство), TanStack Query – за серверный кэш (транзакции, цели, рей-
тинг). Такое разделение избавляет от необходимости дублировать серверные
данные в глобальном сторе и обеспечивает автоматическую инвалидацию и
фоновое обновление данных.
Маршрутизация: React Router v7
React Router обеспечивает клиентскую навигацию между экранами при-
ложения. Переход на отдельные страницы вместо модальных окон – осо-
знанное архитектурное решение, продиктованное ограничениями платформы
Telegram (подробнее в главе 3).
1.3.3 Backend: выбор технологий
Фреймворк: NestJS 11
Для серверной части рассматривались три варианта: Express, Fastify и
NestJS. Express – минималистичный фреймворк без встроенной структу-
ры, что на больших проектах приводит к хаотичной организации кода.
Fastify обеспечивает высокую производительность, но также не диктует ар-
хитектуру. NestJS [10] выбран за модульную архитектуру, встроенный меха-
низм внедрения зависимостей (DI), декораторы и первоклассную поддержку
TypeScript – всё это обеспечивает предсказуемую структуру проекта и упро-
щает его поддержку.
ORM: Prisma 7
Рассматривались Prisma и TypeORM. TypeORM – зрелый инструмент с
поддержкой Active Record и Data Mapper паттернов, однако его типизация не
всегда строга и требует дополнительных настроек. Prisma [12] предоставляет
автогенерацию TypeScript-типов из декларативной схемы, что полностью ис-
ключает рассинхронизацию типов между кодом и базой данных. Кроме того,
Prisma Studio обеспечивает удобный визуальный просмотр данных в процессе
разработки.
База данных: PostgreSQL 16
19
PostgreSQL [11] выбран как надёжная, полнофункциональная реляцион-
ная СУБД с открытым исходным кодом. По сравнению с MySQL PostgreSQL
обеспечивает более строгое соответствие стандартам SQL, лучшую поддерж-
ку сложных типов данных и более развитые механизмы транзакций. Запуск
в Docker-контейнере позволяет изолировать базу данных и упростить пере-
носимость окружения.
Аутентификация: JWT
JSON Web Tokens [7] выбраны для реализации stateless-аутентификации.
После первичной верификации Telegram initData сервер выдаёт подписан-
ный JWT-токен сроком 30 дней, который клиент прикладывает к каждому
запросу. Это исключает необходимость хранить сессии на сервере и упрощает
горизонтальное масштабирование.
1.3.4 Инфраструктура
VPS и операционная система. Приложение развёрнуто на виртуаль-
ном выделенном сервере (VPS) под управлением Ubuntu 24.04 LTS. VPS обес-
печивает полный контроль над окружением в отличие от PaaS-платформ
(Heroku, Railway), которые ограничивают конфигурацию и имеют менее пред-
сказуемое ценообразование.
Nginx. Выступает в роли обратного прокси и веб-сервера: раздаёт ста-
тические файлы React-приложения и проксирует запросы /api/* к NestJS-
бэкенду.
PM2. Менеджер процессов для [Link]: запускает бэкенд-приложение, ав-
томатически перезапускает его при сбоях и обеспечивает запуск при старте
системы.
Docker. PostgreSQL запускается в Docker-контейнере, что изолирует базу
данных от хостовой системы и обеспечивает воспроизводимость окружения.
Cloudflare Tunnel. Обеспечивает защищённое HTTPS-соединение между
пользователями и сервером без необходимости открывать порты и настраи-
вать SSL-сертификаты вручную.
20
1.4 Требования к системе
1.4.1 Функциональные требования
На основе анализа предметной области и обзора аналогов сформулированы
следующие функциональные требования к разрабатываемой системе:
1. Авторизация. Система должна обеспечивать авторизацию пользовате-
ля через аккаунт Telegram без необходимости отдельной регистрации.
2. Учёт транзакций. Пользователь должен иметь возможность добав-
лять, редактировать и удалять финансовые операции с указанием сум-
мы, типа (доход/расход), категории, даты и текстового комментария.
3. Финансовые пространства. Система должна поддерживать несколь-
ко финансовых пространств: личное пространство создаётся автомати-
чески при регистрации; пользователь может создавать совместные про-
странства и приглашать в них других пользователей.
4. Цели накопления. Пользователь должен иметь возможность созда-
вать цели с указанием целевой суммы и дедлайна, вносить взносы и
отслеживать прогресс достижения.
5. Повторяющиеся транзакции. Система должна поддерживать созда-
ние шаблонов транзакций с периодичностью: ежедневно, еженедельно,
ежемесячно.
6. Аналитика. Система должна предоставлять сводку доходов и расходов
по выбранному периоду с разбивкой по категориям.
7. Геймификация. Система должна начислять очки опыта (XP) за ак-
тивность пользователя, отображать текущий уровень и прогресс до сле-
дующего, поддерживать стрик, систему челленджей и таблицу лидеров.
8. Онбординг. При первом входе новый пользователь должен видеть по-
следовательность обучающих экранов, знакомящих с возможностями
приложения.
21
9. Темизация. Приложение должно поддерживать тёмную, светлую и си-
стемную (следует настройке устройства) тему оформления.
10. База знаний. В приложении должен быть доступен раздел с обучаю-
щими статьями по личным финансам.
1.4.2 Нефункциональные требования
1. Производительность. Время отклика API не должно превышать
500 мс при стандартной нагрузке.
2. Кроссплатформенность. Приложение должно корректно работать во
встроенном браузере Telegram на платформах iOS и Android.
3. Безопасность. Все запросы должны передаваться по протоколу
HTTPS. Сервер обязан верифицировать подпись Telegram initData при
каждой авторизации.
4. Надёжность. Система должна обеспечивать целостность данных сред-
ствами реляционной СУБД с поддержкой транзакций.
5. Масштабируемость. Архитектура должна допускать горизонтальное
масштабирование серверной части без изменения кода.
1.5 Постановка задачи
По результатам анализа предметной области, обзора аналогов и формули-
ровки требований определена следующая постановка задачи.
Необходимо разработать систему управления личными финансами
FinQuest в виде Telegram Mini App. Система должна предоставлять поль-
зователю полноценный инструмент финансового учёта – с транзакциями, це-
лями, аналитикой и совместными пространствами – при этом стимулируя
регулярное использование приложения посредством игровых механик: систе-
мы опыта и уровней, ежедневного стрика, временных челленджей и сезонной
таблицы лидеров.
Ключевые отличия разрабатываемой системы от существующих аналогов:
22
• Геймификация как основной инструмент удержания пользователей –
ни один из рассмотренных аналогов не реализует этот подход.
• Telegram Mini App как платформа – обеспечивает нулевой порог вхо-
да и широкий охват аудитории без необходимости установки отдельного
приложения.
• Совместные финансовые пространства – возможность вести общий
бюджет с другими пользователями Telegram.
• Полностью бесплатный доступ ко всем функциям системы.
Разработка системы предполагает создание двух взаимодействующих ком-
понентов: клиентского приложения на React и серверного REST API на
NestJS с базой данных PostgreSQL. Подробное описание архитектурных ре-
шений приводится в следующей главе.
23
Глава 2
Проектирование архитектуры и
моделирование системы
2.1 Общая архитектура системы
2.1.1 Архитектурный стиль и компоненты
Разрабатываемая система FinQuest построена по классической трёхзвен-
ной архитектуре клиент–сервер–база данных. Клиентская часть реализо-
вана как одностраничное веб-приложение (SPA) на базе React, серверная
часть — REST API на NestJS, уровень хранения данных — реляционная
СУБД PostgreSQL.
Ключевой особенностью системы является размещение клиентской части
в экосистеме Telegram в формате Mini App. Это накладывает специфиче-
ские ограничения на клиентскую часть: приложение работает во встроенном
WebView-браузере Telegram, взаимодействует с нативными UI-элементами
мессенджера через JavaScript SDK и получает данные об аутентифициро-
ванном пользователе непосредственно от платформы.
Взаимодействие между компонентами системы осуществляется через
HTTP/HTTPS: клиент обращается к серверу посредством JSON REST API,
сервер работает с базой данных через ORM Prisma. На уровне инфраструкту-
ры обратный прокси-сервер Nginx маршрутизирует входящие запросы: запро-
сы к пути /api/* проксируются к процессу NestJS (порт 3000), все остальные
запросы обслуживаются из директории со статическими файлами собранного
React-приложения.
24
2.1.2 Диаграмма компонентов
На рисунке 2.1 представлена диаграмма компонентов системы, отражаю-
щая логическую структуру приложения и взаимодействие его частей.
Telegram App
(WebView)
HTTPS
Cloudflare Tunnel React SPA
(HTTPS) /*
(статика)
HTTP
Nginx /api/*
NestJS REST API
(обратный прокси) управляет
Prisma ORM
PM2 PostgreSQL 16
(менеджер процессов) (Docker)
Рис. 2.1: Диаграмма компонентов системы FinQuest
2.1.3 Инфраструктурная схема
Система развёрнута на VPS-сервере под управлением Ubuntu 24.04 LTS.
Таблица 2.1 описывает роль каждого инфраструктурного компонента.
Таблица 2.1: Инфраструктурные компоненты системы
Компонент Назначение
VPS (Timeweb Cloud, Ubuntu 24.04) Хостинг всех серверных компонентов системы
Cloudflare Tunnel (cloudflared) Обеспечивает HTTPS-соединение без открытия
портов и ручной настройки SSL-сертификатов
Nginx Обратный прокси: раздача статики React-
приложения и маршрутизация API-запросов к
NestJS
PM2 Менеджер процессов [Link]: запуск, мониторинг
и автоматический перезапуск бэкенда
Docker (postgres:16) Изолированный контейнер с PostgreSQL; обеспе-
чивает воспроизводимость окружения
GitHub Хранилище исходного кода; деплой выполняется
через git pull + сборку на сервере
Cloudflare Tunnel запускается как systemd-сервис и устанавливает посто-
янное зашифрованное соединение между VPS и сетью Cloudflare. Telegram
требует, чтобы URL Mini App работал по HTTPS — данная схема удовле-
творяет этому требованию без необходимости получения и обновления SSL-
сертификата (Let’s Encrypt) вручную.
25
2.2 Проектирование базы данных
2.2.1 Обзор схемы данных
База данных системы FinQuest спроектирована средствами декларативной
схемы Prisma и развёрнута на PostgreSQL 16. Схема включает 14 моделей, ор-
ганизованных в функциональные группы: пользователи и аутентификация,
финансовые пространства, транзакции, цели накопления, геймификация, уве-
домления и контент.
Для первичных ключей всех сущностей используется UUID v4
(@default(uuid())), что обеспечивает глобальную уникальность без
зависимости от централизованной последовательности. Исключение — поле
telegramId типа BigInt, значение которого задаётся платформой Telegram.
2.2.2 Описание сущностей
User (пользователь) — центральная сущность системы. Хранит данные
профиля, полученные от Telegram, настройки и игровые показатели: теку-
щий уровень (level), накопленный опыт (xp), порог следующего уровня
(xpToNext), счётчик ежедневного стрика (streakDays) и дата последней ак-
тивности (lastActiveDate). Настройки пользователя (валюта, тема оформ-
ления, флаги уведомлений) хранятся в денормализованном виде непосред-
ственно в таблице пользователей — данное решение упрощает запросы и со-
ответствует частоте обращений к этим данным.
Space (финансовое пространство) — изолированная область учёта, объ-
единяющая транзакции, цели и челленджи. Тип пространства определяется
перечислением SpaceType: PERSONAL (личное), FAMILY (семейное), WORK (ра-
бочее). При регистрации пользователя автоматически создаётся личное про-
странство.
SpaceMember — связующая таблица «многие ко многим» между User и
Space. Поле role (MemberRole: OWNER / MEMBER) определяет права участника.
Уникальный составной ключ (spaceId, userId) исключает дублирование
членства.
Transaction — основная операционная сущность. Тип определяется пере-
26
числением TransactionType (EXPENSE / INCOME). Поле xpEarned фиксирует
количество начисленного опыта за данную транзакцию. Поле date имеет тип
Date (без времени), что позволяет группировать транзакции по календарным
дням без учёта часового пояса.
Goal — цель накопления. Связана со Space, содержит целевую сумму,
текущий накопленный объём и опциональный дедлайн. Флаг isCompleted и
поле completedAt фиксируют факт и момент достижения цели.
Challenge и UserChallenge — сущности системы челленджей.
Challenge описывает задание (тип, целевое значение, дедлайн, XP-награду).
UserChallenge — персональный прогресс пользователя по конкретно-
му заданию. Тип челленджа задаётся перечислением ChallengeType:
SPENDING_LIMIT (не превысить лимит трат), MIN_SPENDING (потратить не
менее N), CATEGORY_AVOID (избежать трат в категории), STREAK (поддержи-
вать стрик N дней), GOAL_COMPLETE (закрыть цель).
RecurringTransaction — шаблон повторяющейся транзакции. Поле
frequency задаёт периодичность (DAILY / WEEKLY / MONTHLY), nextRunDate —
дату следующего автоматического применения.
KnowledgeArticle — статья базы знаний. Содержит контент, категорию,
оценочное время чтения и XP-награду за прочтение.
SpaceInvite — инвайт-ссылка для добавления пользователя в совмест-
ное пространство. Содержит уникальный UUID-токен и срок действия
(expiresAt).
2.2.3 ER-диаграмма
1..N
User Space SpaceInvite
N 1..N 1..N
1.. 1..N
1.. N
Notification
N
SpaceMember 1.. Goal
1.
.N
1..N
Transaction
1..N
RecurringTransaction UserChallenge Challenge
Рис. 2.2: ER-диаграмма базы данных системы FinQuest (основные сущности)
27
2.2.4 Ключевые связи и ограничения
Каскадное удаление (onDelete: Cascade) применено для всех зависимых
сущностей: удаление пространства автоматически удаляет все транзакции,
цели, членства и инвайты. Удаление пользователя каскадно удаляет его член-
ства в пространствах, пользовательские данные по челленджам и уведомле-
ния. Это обеспечивает ссылочную целостность без риска появления «осиро-
тевших» записей.
Поле spaceId в модели Challenge допускает NULL — глобальные челлен-
джи не привязаны к конкретному пространству и доступны всем пользо-
вателям системы. Пространственно-специфические челленджи привязаны к
конкретному Space.
2.3 Проектирование REST API
2.3.1 Модульная структура NestJS
Серверная часть организована в виде независимых модулей NestJS, каж-
дый из которых инкапсулирует логику одной функциональной области. Такая
структура обеспечивает разделение ответственности и упрощает поддержку
системы.
AppModule
Transactions
AuthModule UsersModule SpacesModule GoalsModule
Module
Challenges Leaderboard Recurring Knowledge
XpModule
Module Module Module Module
PrismaModule
Рис. 2.3: Модульная структура серверного приложения NestJS
2.3.2 Таблица эндпоинтов
Все маршруты API имеют базовый префикс /api. Для защищённых энд-
поинтов применяется JWT-аутентификация через Guard JwtAuthGuard. Таб-
28
лица 2.2 содержит полный перечень эндпоинтов по модулям.
2.3.3 Формат запросов и ответов
Все запросы и ответы используют формат JSON. Для защищённых эндпо-
интов клиент прикладывает JWT-токен в заголовке: Authorization: Bearer
<token>.
Пример ответа эндпоинта POST /auth/telegram:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"firstName": "Ivan",
"level": 3,
"xp": 420,
"xpToNext": 600,
"streakDays": 5,
"onboardingDone": false
}
}
Пример тела запроса POST /transactions:
{
"spaceId": "550e8400-e29b-41d4-a716-446655440000",
"type": "EXPENSE",
"amount": 450.50,
"category": "Coffee",
"categoryEmoji": ":coffee:",
"comment": "Latte",
"date": "2025-04-07"
}
2.4 Проектирование системы аутентификации
2.4.1 Общая схема авторизации
Аутентификация в системе FinQuest основана на двухэтапном механизме:
верификации подлинности пользователя со стороны Telegram и последующей
выдаче внутреннего JWT-токена для последующих запросов к API.
При открытии Mini App платформа Telegram автоматически формиру-
ет строку initData — подписанный набор параметров, включающий данные
29
пользователя (идентификатор, имя, username), временную́ метку и HMAC-
подпись, вычисленную с использованием секрета Telegram-бота. Клиент пе-
редаёт эту строку на сервер в теле POST-запроса к /api/auth/telegram.
2.4.2 Диаграмма последовательности
Telegram NestJS
PostgreSQL
Mini App AuthService
POST /auth/telegram {initData}
HMAC-SHA256 верификация
findUnique(telegramId)
user | null
create(user + Space) [если новый]
user
[Link]({sub: id})
{token, user}
Рис. 2.4: Диаграмма последовательности авторизации пользователя
2.4.3 Верификация HMAC-SHA256
Алгоритм верификации initData реализован в соответствии с официаль-
ной документацией Telegram Bot API и состоит из следующих шагов:
1. Из строки initData (URL-encoded) извлекается значение параметра
hash и удаляется из набора параметров.
2. Оставшиеся параметры сортируются в алфавитном порядке по имени
ключа и объединяются в строку вида key=value, разделённую символом
переноса строки.
3. Вычисляется секретный ключ:
secret_key = HMAC-SHA256(“WebAppData”, BOT _TOKEN )
30
4. Вычисляется ожидаемая подпись:
expected _hash = HMAC-SHA256(secret_key, data_check _string)
5. Ожидаемая подпись побайтово сравнивается с переданным значением
hash. При несовпадении сервер возвращает HTTP 401 Unauthorized.
2.4.4 JWT-токен
После успешной верификации initData сервер выдаёт JSON Web
Token [7], подписанный алгоритмом HS256 с использованием секретного клю-
ча из переменной окружения JWT_SECRET.
Структура payload токена:
{
"sub": "<user-uuid>",
"iat": 1712000000,
"exp": 1714592000 // lifetime: 30 days
}
Токен передаётся клиенту при первом входе и сохраняется в локальном
хранилище браузера. Все последующие запросы к защищённым эндпоинтам
сопровождаются этим токеном в заголовке Authorization. На стороне сер-
вера валидация токена выполняется Guard’ом JwtAuthGuard из стандартного
пакета @nestjs/passport. При истечении срока действия токена клиент ав-
томатически повторяет авторизацию через initData.
Срок жизни токена составляет 30 дней — это обусловлено тем, что у поль-
зователей Telegram Mini App не существует удобного механизма «выхода из
аккаунта», а повторная верификация initData не несёт рисков безопасности,
поскольку выполняется автоматически платформой.
2.5 Проектирование пользовательского интерфейса
2.5.1 Навигационная структура приложения
Пользовательский интерфейс организован вокруг пяти основных экранов,
доступных через панель нижней навигации. Дополнительные экраны (фор-
мы, детали) открываются поверх основных через механизм маршрутизации
31
React Router.
Дашборд Операции Игра Финансы Профиль
Добавить Детали Детали Создать Настройки
транзакцию операции челленджа цель
Выбор Взнос
пространства в цель
Рис. 2.5: Навигационная схема приложения FinQuest
Экран Дашборд является точкой входа в приложение и отображает свод-
ную финансовую информацию активного пространства (баланс, доходы/рас-
ходы текущего месяца), игровой статус пользователя (уровень, XP, стрик) и
быстрый доступ к добавлению транзакции (рис. 2.6).
Экран Операции содержит хронологический список транзакций с воз-
можностью фильтрации по периоду, типу и категории. Добавление новой
транзакции выполняется на отдельной полноэкранной странице, что обуслов-
лено ограничениями платформы Telegram (см. 2.7).
Рис. 2.6: Экран Дашборд: баланс, XP- Рис. 2.7: Экран Операции и форма до-
прогресс, стрик бавления транзакции
Экран Игра объединяет игровые механики: прогресс XP с анимированной
32
шкалой, список активных и доступных челленджей, таблицу лидеров сезона
(рис. 2.8).
Экран Финансы отображает аналитику по пространству: распределение
расходов по категориям, список целей накопления и повторяющихся транзак-
ций (рис. 2.9).
Рис. 2.8: Экран Игра: челленджи и таб- Рис. 2.9: Экран Финансы: аналитика и
лица лидеров цели накопления
Экран Профиль (рис. 2.10) предоставляет доступ к настройкам поль-
зователя, управлению финансовыми пространствами и базе знаний. Форма
добавления транзакции (рис. 2.11) реализована как отдельная страница с
числовой клавиатурой и быстрым выбором категории.
33
Рис. 2.10: Экран Профиль: настройки и
Рис. 2.11: Форма добавления транзакции
пространства
2.5.2 Дизайн-система
Визуальный стиль приложения разработан в концепции «мягкого неомор-
физма» — скруглённые карточки с минимальными тенями на тёмном фоне,
без резких границ и линий.
Цветовая палитра. Акцентные цвета выбраны для смысловой кодиров-
ки элементов:
• #4ADE80 — зелёный (основной акцент, доходы, завершённые цели);
• #FACC15 — жёлтый (XP-награды, стрик, геймификация);
• #F97316 — оранжевый (предупреждения, приближающиеся дедлайны);
• #38BDF8 — голубой (вторичный акцент, информационные элементы).
Типографика. Используется шрифтовое семейство Nunito: Nunito — для
заголовков и числовых значений (округлые формы обеспечивают дружелюб-
ный, ненапрягающий вид), Nunito Sans — для текстового контента и подписей.
Темизация. Система тем реализована через CSS-переменные:
:root {
--color-bg: #0F0F12;
34
--color-card: #1A1A2E;
--color-text: #F1F5F9;
}
[data-theme="light"] {
--color-bg: #F0F4F8;
--color-card: #FFFFFF;
--color-text: #1E293B;
}
Хук useTheme при инициализации приложения устанавливает атрибут
data-theme на корневой элемент документа в соответствии с настройкой
пользователя: dark, light или system (следует настройке операционной си-
стемы через [Link]).
Нативная интеграция с Telegram. SDK Telegram Mini Apps предостав-
ляет набор нативных UI-примитивов:
• BackButton — отображает кнопку «Назад» в шапке Telegram; обрабаты-
вается через useNavigate(-1);
• showConfirm — системный диалог подтверждения (применяется при уда-
лении транзакций);
• HapticFeedback — тактильный отклик при ключевых действиях (добав-
ление транзакции, завершение цели).
2.6 Проектирование системы геймификации
2.6.1 XP-система и уровни
Основу геймификации составляет система опыта (XP — eXperience Points).
Каждое значимое действие пользователя в системе приносит определённое
количество XP в соответствии с таблицей 2.3.
Порог XP для перехода на следующий уровень вычисляется по формуле:
xpToNext(n) = n × 200
где n — текущий уровень пользователя. Таким образом, с каждым уровнем
требования возрастают линейно: переход с уровня 1 на 2 требует 200 XP, с
35
5 на 6 — 1000 XP, с 9 на 10 — 1800 XP. Таблица порогов уровней приведена
ниже.
При добавлении XP сервер применяет следующий алгоритм: к текуще-
му значению xp прибавляется полученное количество очков. Пока xp ≥
xpToNext, из xp вычитается xpToNext, уровень повышается на 1, а xpToNext
пересчитывается по формуле. Это позволяет обрабатывать повышение сразу
на несколько уровней за одно начисление.
2.6.2 Система стрика
Стрик — счётчик дней подряд, в которые пользователь совершал хотя бы
одно действие в приложении (добавлял транзакцию, вносил взнос в цель и
т.д.).
Логика обновления стрика при каждом действии:
1. Если lastActiveDate равна сегодняшней дате — стрик уже обновлён,
ничего не меняем.
2. Если lastActiveDate равна вчерашней дате — инкрементируем
streakDays на 1.
3. В противном случае (пропуск дня или первый вход) — обнуляем
streakDays до 1.
4. Обновляем lastActiveDate текущей датой.
5. При достижении значений 7 и 30 начисляются бонусные XP.
Стрик отображается на экране Дашборд и экране Игра в виде иконки
пламени с числовым счётчиком, что создаёт визуальную мотивацию к еже-
дневному возвращению в приложение.
2.6.3 Система челленджей
Челленджи — временны́е игровые задания с дедлайном и XP-наградой за
выполнение. Система поддерживает пять типов челленджей:
• SPENDING_LIMIT — не превысить установленный лимит трат в ка-
тегории за период;
36
• MIN_SPENDING — потратить не менее заданной суммы в категории
(например, «Инвестируй 5000 руб. в этом месяце»);
• CATEGORY_AVOID — не совершать трат в указанной категории за
период;
• STREAK — поддерживать ежедневный стрик заданное количество
дней;
• GOAL_COMPLETE — завершить цель накопления.
Пользователь Условие
принял выполнено
Доступен Активен Выполнен
Дедлайн
наступил
Провален
Рис. 2.12: Диаграмма состояний пользовательского челленджа
Прогресс по активным челленджам пересчитывается при каж-
дом обращении к списку челленджей. Если прогресс достиг цели —
[Link] устанавливается в true и начисляется XP-
награда. Если наступил дедлайн, а условие не выполнено — isFailed
устанавливается в true.
2.6.4 Таблица лидеров
Таблица лидеров реализована как сезонный рейтинг пользователей по сум-
марному накопленному XP (totalXpEarned). Сезон соответствует двухне-
дельному периоду — это обеспечивает достаточную динамичность рейтин-
га для мотивации при сохранении сроков, за которые пользователь реально
может улучшить свою позицию.
Рейтинг формируется на уровне SQL-запроса с ранжированием по убыва-
нию XP. В интерфейсе отображается позиция текущего пользователя, топ-
10 участников с никнеймами и аватарами, а также количество XP каждого
участника.
Дата следующего сброса сезона отображается в интерфейсе, что создаёт
дополнительную мотивацию к активности перед окончанием периода.
37
Таблица 2.2: Эндпоинты REST API системы FinQuest
Метод Путь Описание
Auth
POST /auth/telegram Аутентификация по Telegram initData; воз-
вращает JWT и профиль пользователя
Users
GET /users/me Профиль текущего пользователя
PATCH /users/me Обновление настроек пользователя (тема,
валюта, уведомления)
GET /users/me/notifications Список уведомлений
PATCH /users/me/notifications/read-all Отметить все уведомления как прочитан-
ные
Spaces
GET /spaces Список пространств пользователя
POST /spaces Создать новое пространство
GET /spaces/:id Данные конкретного пространства
PATCH /spaces/:id Обновить пространство
DELETE /spaces/:id Удалить пространство
GET /spaces/:id/members Участники пространства
POST /spaces/:id/invite Создать инвайт-ссылку
POST /spaces/join/:token Присоединиться по токену
DELETE /spaces/:id/members/:userId Исключить участника
Transactions
GET /transactions Список транзакций (с фильтрами: spaceId,
dateFrom, dateTo)
POST /transactions Создать транзакцию; начисляет XP
PATCH /transactions/:id Редактировать транзакцию
DELETE /transactions/:id Удалить транзакцию
GET /transactions/analytics Аналитика по периоду и категориям
Goals
GET /goals Цели в пространстве
POST /goals Создать цель
PATCH /goals/:id Обновить цель / внести взнос
DELETE /goals/:id Удалить цель
Challenges
GET /challenges Доступные и активные челленджи пользо-
вателя
POST /challenges/:id/join Принять участие в челлендже
Leaderboard
GET /leaderboard Сезонная таблица лидеров
Recurring Transactions
GET /recurring Список шаблонов повторяющихся транзак-
ций
POST /recurring Создать шаблон
PATCH /recurring/:id Обновить шаблон
DELETE /recurring/:id Удалить шаблон
Knowledge
GET /knowledge Список статей базы знаний
GET /knowledge/:id Содержимое статьи
38
Таблица 2.3: Правила начисления XP
Действие XP
Добавление транзакции 10
Первая транзакция (единоразово) 30
Достижение стрика 7 дней 70
Достижение стрика 30 дней 300
Выполнение цели накопления 100
Завершение челленджа от 50 (зависит от сложности)
Прочтение статьи базы знаний 20
Таблица 2.4: Пороги уровней XP
Уровень XP для перехода Накопленный XP (минимум)
1 200 0
2 400 200
3 600 600
4 800 1 200
5 1 000 2 000
6 1 200 3 000
7 1 400 4 200
8 1 600 5 600
9 1 800 7 200
10 2 000 9 000
39
Глава 3
Особенности реализации и тестирование
3.1 Реализация серверной части
3.1.1 Структура проекта NestJS
Серверная часть приложения разработана на фреймворке NestJS 11 и ор-
ганизована в модульную структуру, соответствующую функциональным об-
ластям системы. Каждый модуль содержит контроллер (Controller), сервис
(Service), модульный файл (Module) и DTO-классы для валидации входных
данных.
src/
[Link] # root module
[Link] # entry point
auth/ # authentication module
[Link]
[Link]
[Link]
[Link]
[Link]
dto/
prisma/ # database access layer
[Link]
[Link]
users/
spaces/
transactions/
goals/
challenges/
xp/ # XP and streak business logic
leaderboard/
recurring/ # recurring transactions
knowledge/
bot/ # Telegram bot notifications
40
generated/ # Prisma client output
prisma/
Точка входа [Link] выполняет инициализацию приложения: регистри-
рует глобальный префикс /api, подключает ValidationPipe с флагами
whitelist: true и transform: true (автоматическое преобразование типов
DTO) и включает CORS для взаимодействия с фронтендом. В этом же файле
применяется глобальный патч сериализации BigInt:
([Link] as any).toJSON = function () {
return [Link]();
};
Данный патч необходим потому, что поле telegramId имеет тип BigInt —
идентификаторы Telegram выходят за диапазон безопасных 53-битных це-
лых JavaScript, а встроенный [Link] не поддерживает сериализа-
цию BigInt. Патч применяется один раз при старте приложения и действует
глобально.
3.1.2 PrismaService: паттерн композиции
Доступ к базе данных инкапсулирован в сервисе PrismaService. Вместо
распространённого подхода с наследованием (extends PrismaClient) при-
меняется паттерн композиции — экземпляр PrismaClient создаётся как
приватное поле, а доступ к каждой модели обеспечивается через геттеры:
@Injectable()
export class PrismaService implements OnModuleInit {
private readonly _client: InstanceType<typeof PrismaClient>;
constructor() {
const adapter = new PrismaPg({
connectionString: [Link].DATABASE_URL!
});
this._client = new PrismaClient({ adapter });
}
get user() { return this._client.user; }
get transaction() { return this._client.transaction; }
get space() { return this._client.space; }
// ... other model getters
async onModuleInit() {
await this._client.$connect();
41
}
}
Данное решение обусловлено конфликтом между механизмом внедрения
зависимостей NestJS и моделью наследования PrismaClient: при использова-
нии extends контейнер DI некорректно обрабатывает методы базового класса
в контексте ESM/CommonJS модулей. Паттерн композиции полностью устра-
няет эту проблему без потери функциональности.
Дополнительно, использование Prisma Postgres Adapter (PrismaPg) вме-
сто встроенного адаптера обеспечивает прямое управление подключением и
улучшает совместимость с Prisma 7.
3.1.3 Модуль авторизации
Ключевая особенность реализации AuthModule — использование
[Link] вместо [Link]:
[Link]({
useFactory: () => ({
secret: [Link].JWT_SECRET,
signOptions: { expiresIn: '30d' },
}),
})
Причина: [Link] читает значение [Link].JWT_SECRET
в момент декларации модуля — до того, как NestJS выполнит загрузку
переменных окружения из файла .env. В результате секрет оказывается
undefined, и все JWT-токены подписываются пустым ключом. Фабричная
функция useFactory вызывается отложенно — уже после инициализации
окружения.
Алгоритм верификации initData в методе validateInitData реализует
требования документации Telegram Bot API:
private validateInitData(initData: string) {
const params = new URLSearchParams(initData);
const hash = [Link]('hash');
[Link]('hash');
const dataCheckString = [...[Link]()]
.sort(([a], [b]) => [Link](b))
.map(([k, v]) => `${k}=${v}`)
.join('\n');
42
const secretKey = createHmac('sha256', 'WebAppData')
.update(botToken).digest();
const expectedHash = createHmac('sha256', secretKey)
.update(dataCheckString).digest('hex');
if (expectedHash !== hash)
throw new UnauthorizedException('Invalid hash');
return [Link]([Link]('user')!);
}
3.1.4 Модуль транзакций и начисление XP
При создании транзакции сервис последовательно выполняет несколько
операций: создание записи в базе данных, обновление стрика пользователя
и начисление XP. Метод addXp реализует обработку нескольких повышений
уровня за одно начисление:
async addXp(userId: string, amount: number) {
const user = await [Link]
.findUniqueOrThrow({ where: { id: userId } });
let { xp, level, xpToNext, totalXpEarned } = user;
xp += amount;
totalXpEarned += amount;
while (xp >= xpToNext) {
xp -= xpToNext;
level += 1;
xpToNext = level * 200;
}
return [Link]({
where: { id: userId },
data: { xp, level, xpToNext, totalXpEarned }
});
}
Логика обновления стрика учитывает три состояния: активность уже за-
фиксирована сегодня (пропустить), последняя активность была вчера (ин-
крементировать), в остальных случаях (пропуск или первый вход) сбросить
до 1. При достижении стриков 7 и 30 дней начисляются бонусные XP — 70 и
300 соответственно.
43
3.1.5 Модуль повторяющихся транзакций
Автоматическое применение повторяющихся транзакций реализовано че-
рез планировщик @nestjs/schedule с CRON-расписанием:
@Cron('0 * * * *') // every hour at :00
async processRecurring() {
const due = await [Link]({
where: { isActive: true, nextRunDate: { lte: new Date() } },
});
for (const rec of due) {
await [Link]({
data: {
spaceId: [Link], userId: [Link],
type: [Link], amount: [Link],
category: [Link],
date: new Date(), xpEarned: 10,
}
});
await [Link]([Link]);
await [Link]([Link], 10);
// Advance nextRunDate by one period
const next = new Date([Link]);
if ([Link] === 'DAILY')
[Link]([Link]() + 1);
else if ([Link] === 'WEEKLY')
[Link]([Link]() + 7);
else if ([Link] === 'MONTHLY')
[Link]([Link]() + 1);
await [Link]({
where: { id: [Link] },
data: { nextRunDate: next }
});
}
}
Планировщик запускается ежечасно и обрабатывает все шаблоны, у ко-
торых nextRunDate не позже текущего момента. После создания транзак-
ции дата следующего запуска сдвигается вперёд. Такой подход устойчив к
перезапускам сервиса: если бэкенд был недоступен, при первом запуске все
просроченные шаблоны будут обработаны корректно.
44
3.1.6 Модуль финансовых пространств
Модуль SpacesModule реализует управление финансовыми пространства-
ми — основной организационной единицей системы. Ключевой особенностью
является механизм приглашений по токену.
При создании инвайт-ссылки сервис проверяет, что пространство не яв-
ляется личным (добавление участников в личное пространство запрещено),
затем создаёт запись SpaceInvite с UUID-токеном и сроком действия 7 дней.
На основе токена формируется глубокая ссылка Telegram:
async createInvite(userId: string, spaceId: string) {
const expiresAt = new Date();
[Link]([Link]() + 7);
const invite = await [Link]({
data: { spaceId, createdBy: userId, expiresAt }
});
const inviteUrl =
`[Link]
return { token: [Link], expiresAt, inviteUrl };
}
При переходе по ссылке Telegram передаёт параметр
startapp=invite_{token} в initData, фронтенд извлекает токен и вы-
зывает POST /spaces/join/:token. Сервис проверяет токен: он должен
существовать, не быть использованным (usedAt равно null) и не быть про-
сроченным. После успешной проверки токен помечается как использованный,
пользователь добавляется через upsert (идемпотентная операция — по-
вторный переход не создаст дублирующую запись), владельцу пространства
отправляется уведомление:
async joinByToken(userId: string, token: string) {
const invite = await [Link]({
where: { token }, include: { space: true }
});
if (!invite) throw new NotFoundException();
if ([Link]) throw new BadRequestException('Already used');
if ([Link] < new Date())
throw new BadRequestException('Expired');
await [Link]({
where: { id: [Link] }, data: { usedAt: new Date() }
45
});
await [Link]({
where: { spaceId_userId: { spaceId: [Link], userId } },
create: { spaceId: [Link], userId, role: 'MEMBER' },
update: {},
});
}
Защита прав доступа реализована на уровне каждого метода: изменение
и удаление пространства доступно только владельцу ([Link] ===
userId), выход из пространства — всем участникам кроме владельца.
3.1.7 Модуль целей накопления
GoalsService реализует логику целей накопления с автоматическим за-
вершением при достижении целевой суммы. Ключевая часть — обработка
взноса в цели в методе updateGoal:
if ([Link] !== undefined) {
[Link] = [Link];
// Auto-complete when target reached
if ([Link] >= [Link] && ![Link]) {
[Link] = true;
[Link] = new Date();
await [Link](userId, 100);
// Notify all space members via Telegram bot
const members = await [Link]({
where: { spaceId: [Link] },
include: { user: true },
});
for (const m of members) {
if ([Link])
await [Link]([Link], msg);
}
}
}
При достижении цели система автоматически: устанавливает флаг
isCompleted и фиксирует дату completedAt, начисляет 100 XP пользовате-
лю, совершившему последний взнос, и рассылает уведомление всем участни-
кам пространства через Telegram Bot API. Это реализует социальный аспект
геймификации — достижение цели в совместном пространстве становится со-
46
бытием для всей группы.
3.1.8 Модуль челленджей
Центральным элементом ChallengesService является метод
computeProgress — функция вычисления текущего прогресса пользо-
вателя по челленджу. Прогресс пересчитывается при каждом обращении
к списку челленджей, что позволяет не хранить его в базе как постоянное
значение, а вычислять на основе актуальных данных о транзакциях и
активности.
Логика расчёта зависит от типа челленджа:
private async computeProgress(ch, uc, userId, user) {
switch ([Link]) {
case 'STREAK':
return {
currentValue: [Link],
isCompleted: [Link] >= [Link],
isFailed: false,
};
case 'SPENDING_LIMIT': {
const sum = await [Link](userId, 'EXPENSE', ch);
const deadlinePassed = new Date([Link]) < new Date();
return {
currentValue: sum,
isCompleted: deadlinePassed && sum <= [Link],
isFailed: sum > [Link],
};
}
case 'CATEGORY_AVOID': {
if ([Link])
return { currentValue: 0, isCompleted: false, isFailed: true };
const violation = await [Link]({
where: {
userId, type: 'EXPENSE',
date: { gte: new Date([Link]) },
category: { contains: [Link], mode: 'insensitive' },
},
});
const daysSince = [Link](
([Link]() - new Date([Link]).getTime()) / 86400000
);
return {
47
currentValue: daysSince,
isCompleted: !violation && daysSince >= [Link],
isFailed: !!violation,
};
}
case 'GOAL_COMPLETE': {
const count = await [Link]({
where: { spaceId: { in: spaceIds }, isCompleted: true }
});
return {
currentValue: count,
isCompleted: count >= [Link],
isFailed: false,
};
}
}
}
Для типов SPENDING_LIMIT и MIN_SPENDING агрегация суммы транзакций
выполняется с учётом настраиваемого периода челленджа (periodType: ме-
сяц, неделя, произвольное число дней). Если прогресс изменился с момента
последнего обращения, UserChallenge обновляется в базе данных, а при пер-
вом завершении начисляется XP-награда.
3.1.9 Модуль аналитики
AnalyticsService реализует детальную аналитику расходов и доходов
за выбранный месяц. Метод getSummary выполняет параллельную загрузку
транзакций текущего и предыдущего месяца, после чего производит серию
агрегаций на уровне приложения:
const [transactions, prevTransactions] = await [Link]([
[Link]({
where: { spaceId, date: { gte: from, lt: to } }
}),
[Link]({
where: { spaceId, date: { gte: prevFrom, lt: prevTo } }
}),
]);
// Aggregate by category
const catMap = new Map<string, { emoji: string; amount: number }>();
for (const t of [Link](t => [Link] === 'EXPENSE')) {
const prev = [Link]([Link]) ?? { emoji: [Link], amount: 0 };
48
[Link]([Link], { ...prev, amount: [Link] + [Link] });
}
const byCategory = [...[Link]()].map(([category, v]) => ({
category, emoji: [Link], amount: [Link],
percent: expense > 0 ? [Link](([Link] / expense) * 100) : 0,
}));
Помимо базовой разбивки по категориям, сервис вычисляет ряд поведен-
ческих метрик:
• Динамика по дням — сводка доходов и расходов на каждый день ме-
сяца (byDay);
• Самый дорогостоящий день недели — агрегация расходов по дням
недели, определение максимума;
• Наиболее частая категория — категория с наибольшим числом тран-
закций;
• Стрик экономии — количество дней подряд, в которые расходы
не превысили дневной лимит (рассчитывается как monthlyBudget /
daysInMonth либо на основе среднедневных расходов предыдущего ме-
сяца);
• Сравнение с предыдущим месяцем — абсолютные значения дохо-
дов, расходов и баланса за предыдущий период.
3.1.10 Модуль таблицы лидеров
LeaderboardService реализует сезонный рейтинг с фиксированной двух-
недельной длительностью сезона. Начало эпохи (первого сезона) задано кон-
стантой EPOCH = 2026-01-05, номер текущего сезона и его границы вычис-
ляются детерминированно:
const EPOCH = new Date('2026-01-05T00:00:00.000Z');
const SEASON_DAYS = 14;
currentSeasonInfo() {
const ms = [Link](0, [Link]() - [Link]());
const seasonNumber = [Link](ms / (SEASON_DAYS * 86400000));
const seasonStart = new Date(
[Link]() + seasonNumber * SEASON_DAYS * 86400000
49
);
const seasonEnd = new Date(
[Link]() + SEASON_DAYS * 86400000
);
const daysLeft = [Link](([Link]() - [Link]()) / 86400000);
return { seasonNumber, seasonStart, seasonEnd, daysLeft };
}
Рейтинг за сезон строится на основе суммы xpEarned по транзакциям за
период сезона через groupBy:
const grouped = await [Link]({
by: ['userId'],
where: { date: { gte: from, lt: to } },
_sum: { xpEarned: true },
orderBy: { _sum: { xpEarned: 'desc' } },
take: 50,
});
Детерминированный расчёт номера сезона позволяет не хранить его в базе
данных — клиент и сервер независимо вычисляют текущие границы сезона по
одной формуле. Сброс рейтинга происходит автоматически: по истечении се-
зона запросы начинают фильтровать транзакции уже за новый период. Пер-
вые три места получают XP-награды (SEASON_REWARDS: 500, 250 и 100 XP),
которые начисляются вручную администратором по итогам сезона.
3.2 Реализация клиентской части
3.2.1 Структура React-проекта
Клиентская часть реализована как одностраничное приложение на React
19 с Vite в качестве сборщика. Структура исходных файлов:
src/
api/ # axios instances + typed API functions
components/ # reusable UI components
hooks/ # custom React hooks
[Link]
[Link]
[Link] # number and date formatting
pages/ # full-screen page components
store/ # Zustand global store
[Link]
types/ # shared TypeScript type definitions
[Link] # root component with router
50
[Link] # application entry point
[Link] # global styles + CSS variables
Все страницы являются полноэкранными React-компонентами, подклю-
чёнными через React Router v7. Для каждой функциональной области
выделена отдельная страница: DashboardPage, OperationsPage, GamePage,
FinancePage, ProfilePage, а также страницы-формы (AddTransactionPage,
GoalFormPage и др.).
3.2.2 Разделение состояния: Zustand и TanStack Query
Управление состоянием реализовано по двухуровневой модели, чётко раз-
граничивающей клиентское и серверное состояние.
Zustand отвечает за глобальное клиентское состояние — данные аутенти-
фицированного пользователя и идентификатор активного финансового про-
странства. Эти данные не требуют обновления с сервера при каждом рендере,
но должны быть доступны из любого компонента приложения:
interface AppStore {
user: User | null;
activeSpaceId: string | null;
setUser: (user: User | null) => void;
setActiveSpaceId: (id: string) => void;
}
const useAppStore = create<AppStore>((set) => ({
user: null,
activeSpaceId: null,
setUser: (user) => set({ user }),
setActiveSpaceId: (id) => set({ activeSpaceId: id }),
}));
TanStack Query v5 управляет серверным кэшем — транзакциями, целя-
ми, челленджами, таблицей лидеров. После мутирующих операций (создание
транзакции, обновление цели) вызывается [Link],
что инициирует повторную загрузку актуальных данных:
const { mutate: addTx } = useMutation({
mutationFn: [Link],
onSuccess: () => {
[Link]({ queryKey: ['transactions'] });
[Link]({ queryKey: ['user'] });
}
51
});
Такое разделение исключает необходимость дублировать серверные дан-
ные в глобальном сторе и предотвращает проблему устаревших данных при
навигации между страницами.
3.2.3 Интеграция с Telegram Mini App SDK
Telegram Mini App SDK предоставляет JavaScript API для взаимодействия
с нативными элементами мессенджера. В приложении используются три клю-
чевых механизма.
BackButton — кнопка «Назад» в заголовке Telegram. Регистрируется и
управляется через эффект:
useEffect(() => {
[Link]();
[Link](
() => navigate(-1)
);
return () => [Link]();
}, [navigate]);
showConfirm — системный диалог подтверждения. Применяется при уда-
лении транзакций и возвращает результат выбора пользователя в коллбэке:
[Link](
'Delete this transaction?',
(confirmed) => { if (confirmed) deleteTx(id); }
);
HapticFeedback — тактильный отклик устройства. Вызывается при
успешном добавлении транзакции и при ошибках, что создаёт нативное ощу-
щение взаимодействия.
В хуке useAuth реализована защита от бесконечного цикла перезапросов:
перехватчик Axios при получении статуса 401 проверяет, не является ли те-
кущий запрос эндпоинтом авторизации (!isAuthEndpoint), и только в про-
тивном случае инициирует повторный вход.
52
3.2.4 Темизация приложения
Система тем реализована на основе CSS-переменных и HTML-атрибута
data-theme. Хук useTheme устанавливает тему в соответствии с настройкой
пользователя при инициализации приложения:
export function useTheme(theme?: string) {
useEffect(() => {
if (theme === 'system') {
const mq = [Link](
'(prefers-color-scheme: light)'
);
const apply = (e: MediaQueryListEvent | MediaQueryList) =>
[Link](
'data-theme', [Link] ? 'light' : 'dark'
);
apply(mq);
[Link]('change', apply);
return () => [Link]('change', apply);
}
[Link](
'data-theme', theme ?? 'dark'
);
}, [theme]);
}
При значении system хук подписывается на медиа-запрос
prefers-color-scheme и реактивно переключает тему при изменении
системных настроек. Все компоненты используют только CSS-переменные,
не хардкодя цвета напрямую.
3.2.5 Онбординг и оптимистичное обновление
При первом входе пользователя (поле onboardingDone: false) отобража-
ется трёхэкранный онбординг (рис. 3.1). Была выявлена проблема: при немед-
ленном вызове navigate(’/’) после API-запроса компонент роутера успевал
перерендериться и снова показывал онбординг, поскольку кэш useQuery ещё
не обновился.
53
Рис. 3.1: Экран онбординга при первом входе
Решение — оптимистичное обновление глобального стора до вызова нави-
гации:
const handleFinish = async () => {
// Optimistic: update store immediately so router
// sees onboardingDone = true on re-render
setUser({ ...user!, onboardingDone: true });
navigate('/');
// Actual API call runs in background
await [Link]({ onboardingDone: true });
};
Таким образом, компонент роутера при перерендере уже видит актуальное
значение флага в сторе и не перенаправляет на онбординг повторно.
3.2.6 Обход ограничений скролла Telegram
В процессе разработки была обнаружена принципиальная особенность
платформы: Telegram WebApp блокирует нативный скролл внутри элемен-
тов с позиционированием position: fixed (bottom sheets, модальные окна).
Попытки открыть выпадающие списки или пикеры дат в виде наложений
54
приводили к невозможности прокрутки их содержимого.
Принятое архитектурное решение — полный отказ от модальных окон и
bottom sheets в пользу отдельных полноэкранных страниц. Каждая форма
(добавление транзакции, выбор категории, пикер даты, форма цели) реализо-
вана как самостоятельный роут React Router. Переход к форме выглядит как
переход на новую страницу, кнопка «Назад» Telegram корректно обрабаты-
вает возврат. Этот подход полностью решает проблему скролла и улучшает
UX: каждая форма имеет собственный URL, что позволяет использовать на-
тивную историю браузера.
3.3 Инфраструктура и деплой
3.3.1 Конфигурация Nginx
Nginx выполняет роль обратного прокси и файлового сервера для статики
React-приложения. Конфигурационный блок сервера:
server {
listen 80;
server_name localhost;
root /var/www/finquest-frontend/dist;
index [Link];
# API proxy -> NestJS backend
location /api/ {
proxy_pass [Link]
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# SPA fallback: all routes serve [Link]
location / {
try_files $uri $uri/ /[Link];
}
}
Директива try_files $uri $uri/ /[Link] реализует поддержку
SPA-роутинга: при запросе любого пути, не соответствующего физическо-
му файлу, Nginx возвращает [Link], после чего React Router выполняет
клиентскую маршрутизацию. Без этой директивы прямая ссылка на внут-
55
реннюю страницу приложения возвращала бы 404.
3.3.2 PM2: менеджер процессов
PM2 обеспечивает запуск и мониторинг серверного процесса [Link]. Кон-
фигурационный файл [Link]:
[Link] = {
apps: [{
name: 'finquest-backend',
script: 'dist/[Link]',
cwd: '/var/www/finquest-backend',
env: {
NODE_ENV: 'production',
PORT: 3000,
},
restart_delay: 3000,
max_restarts: 10,
autorestart: true,
}]
};
PM2 автоматически перезапускает приложение при сбоях (с задержкой
3 с) и регистрируется в systemd для запуска при старте сервера командой pm2
startup. Деплой новой версии бэкенда: git pull && npm run build && pm2
restart finquest-backend.
3.3.3 Docker Compose для PostgreSQL
База данных PostgreSQL 16 запускается в Docker-контейнере. Файл
[Link]:
version: '3.9'
services:
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: postgres
POSTGRES_DB: finquest
ports:
- '5432:5432'
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
56
pgdata:
Именованный том pgdata гарантирует сохранность данных при перезапус-
ке или обновлении контейнера. Образ postgres:16-alpine выбран за ком-
пактный размер (около 80 МБ против 350 МБ у стандартного образа) при
полной функциональной совместимости.
3.3.4 Cloudflare Tunnel
Cloudflare Tunnel (cloudflared) устанавливает постоянное исходящее со-
единение от VPS к сети Cloudflare, обеспечивая HTTPS-доступ к приложению
без необходимости открывать входящие порты и получать SSL-сертификаты
вручную. Туннель настроен как systemd-сервис и запускается автоматически
при старте системы.
В ходе разработки была выявлена особенность: при использовании времен-
ного туннеля Cloudflare генерирует случайный публичный URL при каждом
запуске. Поскольку URL Mini App фиксируется в настройках Telegram-бота
через BotFather, каждый перезапуск туннеля требовал ручного обновления
этого адреса. Для продакшен-окружения необходим именованный туннель с
постоянным доменным именем.
3.4 Тестирование
3.4.1 Стратегия тестирования
С учётом специфики разработанной системы — интерактивного клиент-
ского приложения с богатой пользовательской логикой, работающего поверх
Telegram Mini App, — была выбрана многоуровневая стратегия тестирования,
ориентированная прежде всего на функциональную корректность и пользова-
тельский опыт. Цель тестирования — убедиться в том, что система полностью
реализует заявленные функциональные требования (см. главу 2), корректно
обрабатывает граничные ситуации и обеспечивает приемлемое качество вза-
имодействия для конечного пользователя.
Тестирование системы FinQuest проводилось в три этапа:
57
1. Тестирование API (backend integration testing) — проверка коррект-
ности серверных эндпоинтов с помощью Postman: соответствие ответов
ожидаемым форматам, корректность кодов состояния HTTP, правиль-
ность бизнес-логики (начисление XP, создание пространств при реги-
страции, обработка ошибок авторизации, валидация входных данных).
Применялся подход «чёрного ящика»: тестировщик располагает только
описанием контракта эндпоинта, не зная внутренней реализации.
2. Функциональное тестирование (system testing) — сквозная проверка
пользовательских сценариев в Telegram Mini App на реальных устрой-
ствах под управлением iOS и Android. На этом этапе применялись
как заранее подготовленные тест-кейсы, так и поисковое тестирование
(exploratory testing) для выявления неожиданных проблем интерфейса.
3. Пользовательское приёмочное тестирование (User Acceptance
Testing, UAT) — проведение опроса среди группы потенциальных поль-
зователей с целью получить независимую оценку удобства, понятности
и мотивирующего потенциала интерфейса.
Автоматическое модульное тестирование (unit testing) и end-to-end тести-
рование с использованием инструментов вроде Playwright или Cypress не при-
менялись осознанно. В контексте учебного проекта с фиксированными срока-
ми приоритет был отдан функциональной полноте, сквозной проверке поль-
зовательских сценариев и сбору обратной связи от живых пользователей —
это даёт более прямой ответ на вопрос о пригодности продукта к реальному
использованию. Тем не менее, модульная структура NestJS и чёткое разде-
ление слоёв (контроллер, сервис, доступ к данным) позволяют в дальнейшем
без существенной переработки добавить unit-тесты для бизнес-сервисов и ин-
теграционные тесты с использованием тестовой базы данных.
3.4.2 Тестовое окружение
Тестирование выполнялось в окружении, максимально приближённом к
продакшен-конфигурации. Серверная часть приложения была развёрнута на
VPS под управлением Ubuntu Server 24.04 LTS (2 vCPU, 4 ГБ ОЗУ); клиент-
58
ская часть — проксировалась через Cloudflare Tunnel, что обеспечивало ва-
лидный TLS-сертификат и доступность по постоянному HTTPS-адресу. Для
функционального и пользовательского тестирования применялись реальные
устройства; список приведён в таблице 3.1.
Таблица 3.1: Тестовое окружение
Категория Платформа Конфигурация
Сервер Ubuntu 24.04 [Link] 20 LTS, NestJS 11, PostgreSQL 16 (Docker), Nginx
1.24, PM2 5
Клиент A iOS 18 iPhone 13, Telegram 11.x
Клиент B Android 14 Samsung Galaxy S22, Telegram 11.x
Клиент C Android 13 Xiaomi Redmi Note 11, Telegram 10.x
Клиент D macOS 15 Telegram Desktop (для проверки fallback-поведения)
Инструменты — Postman 11, Chrome DevTools, Telegram WebApp
Inspector
Использование нескольких разных устройств позволило проверить кор-
ректность отображения интерфейса при различных значениях safe-area, а
также воспроизведение тактильной обратной связи (HapticFeedback) на
устройствах разных производителей.
3.4.3 Тестирование API
Тестирование серверных эндпоинтов выполнялось в Postman. Для ими-
тации авторизации использовался JWT-токен, выданный сервером после ве-
рификации реального initData. Таблица 3.2 содержит результаты проверки
ключевых эндпоинтов.
3.4.4 Функциональное тестирование
Функциональное тестирование выполнялось путём ручной проверки поль-
зовательских сценариев в Telegram Mini App. Таблица 3.3 содержит резуль-
таты проверки основных тест-кейсов.
3.4.5 Пользовательское приёмочное тестирование
Принципиальной особенностью системы FinQuest является ориентация на
конечного пользователя: ключевая гипотеза работы — что геймификация по-
вышает регулярность использования приложения — может быть проверена
59
Таблица 3.2: Результаты тестирования API
Эндпоинт Сценарий HTTP Результат
POST /auth/telegram Корректный initData 200 JWT и профиль пользова-
теля
POST /auth/telegram Неверный hash 401 Unauthorized
POST /auth/telegram Повторный вход 200 Существующий пользова-
тель
GET /users/me Валидный JWT 200 Профиль с XP, уровнем,
стриком
GET /users/me Истёкший JWT 401 Unauthorized
POST /transactions Корректные данные 201 Транзакция создана, XP
начислен
POST /transactions Чужое пространство 403 Forbidden
POST /spaces/join/:token Валидный токен 200 Пользователь добавлен
POST /spaces/join/:token Истёкший токен 400 Bad Request
GET /leaderboard Авторизованный 200 Список участников с XP
GET /challenges Авторизованный 200 Прогресс пересчитан и воз-
вращён
только на живых респондентах. По этой причине помимо формального функ-
ционального тестирования была проведена сессия пользовательского приё-
мочного тестирования (UAT) с участием небольшой группы потенциальных
пользователей.
Состав респондентов. В опросе приняли участие 8 знакомых автора в
возрасте от 19 до 24 лет (средний возраст — 21 год), все — студенты Петроза-
водского государственного университета (5 респондентов с направления «Ин-
формационные системы и технологии», 3 — с других направлений). Шесть
человек ранее имели опыт использования хотя бы одного из приложений-
аналогов (Monefy, CoinKeeper, Дзен-мани, банковские приложения с функ-
цией учёта расходов), что позволило получить осмысленные сравнительные
оценки.
Сценарий тестирования. Каждому респонденту было предложено в те-
чение 10–15 минут выполнить набор типовых сценариев в реальном Telegram
Mini App, развёрнутом на тестовом стенде:
1. войти в приложение через Telegram, пройти онбординг;
2. добавить от трёх до пяти транзакций (как минимум один доход и два
расхода в разных категориях);
3. создать цель накопления и внести в неё взнос;
60
Таблица 3.3: Функциональные тест-кейсы
Тест-кейс Ожидаемый результат Статус
TC-01: Первый вход нового пользователя Онбординг из 3 экранов, затем Пройден
Дашборд
TC-02: Повторный вход Дашборд без онбординга Пройден
TC-03: Добавление расхода Транзакция в списке, XP +10, Пройден
стрик обновлён
TC-04: Добавление дохода Транзакция в списке, баланс Пройден
увеличен
TC-05: Создание совместного пространства Пространство в списке, ссылка Пройден
доступна
TC-06: Присоединение по инвайту Пользователь добавлен как Пройден
участник
TC-07: Создание цели накопления Цель с прогресс-баром на Пройден
экране Финансы
TC-08: Взнос в цель Прогресс обновлён, XP при за- Пройден
вершении
TC-09: Повторяющаяся транзакция Шаблон создан, применяется Пройден
автоматически
TC-10: Принятие челленджа Прогресс отображается на Пройден
экране Игра
TC-11: Смена темы оформления Тема переключается мгновен- Пройден
но без перезагрузки
TC-12: Таблица лидеров Рейтинг и позиция пользовате- Пройден
ля отображены
TC-13: Удаление транзакции Нативный диалог Telegram, Пройден
удаление после подтверждения
TC-14: Прочтение статьи базы знаний Статья открылась, XP +20 на- Пройден
числен
4. принять один из доступных челленджей;
5. ознакомиться с экраном «Финансы» и просмотреть аналитику;
6. открыть таблицу лидеров и оценить геймификационный контур.
После выполнения сценария каждому респонденту предлагалось оценить
пять характеристик приложения по 5-балльной шкале Лайкерта (1 — крайне
неудобно/непонятно, 5 — очень удобно/понятно). Оценивались следующие
критерии:
• C1 — удобство добавления транзакций;
• C2 — понятность интерфейса в целом и навигационной модели;
61
• C3 — мотивационный потенциал геймификации (XP, уровни, стрик, чел-
ленджи);
• C4 — визуальная привлекательность дизайна;
• C5 — полезность аналитики и сводных отчётов.
Дополнительно задавался закрытый вопрос: «Готовы ли вы использовать
данное приложение на постоянной основе?» — с вариантами «Да», «Скорее
да», «Скорее нет», «Нет» — и предлагалось оставить произвольный коммента-
рий с замечаниями и пожеланиями. Сводные оценки по критериям приведены
в таблице 3.4.
Таблица 3.4: Результаты пользовательского опроса (балльные оценки)
Респондент C1 C2 C3 C4 C5 Среднее
R1 5 5 4 5 4 4,6
R2 5 4 5 5 5 4,8
R3 4 5 5 5 4 4,6
R4 5 4 4 5 5 4,6
R5 4 4 3 4 4 3,8
R6 5 5 5 5 4 4,8
R7 4 5 4 4 3 4,0
R8 5 5 5 5 5 5,0
Среднее 4,6 4,6 4,4 4,8 4,3 4,5
На вопрос о готовности использовать приложение на постоянной основе от-
веты распределились следующим образом: «Да» — 5 человек, «Скорее да» —
2 человека, «Скорее нет» — 1 человек, «Нет» — 0 человек. Таким образом,
доля положительных ответов составила 87,5%.
Качественный анализ комментариев. Свободные ответы респонден-
тов были обобщены и распределены на положительные впечатления и заме-
чания/пожелания.
Среди положительных впечатлений наиболее часто упоминались:
• ввод транзакции в два-три касания признан удобным; респонденты от-
мечали, что такой сценарий не вызывает желания «отложить на потом»,
что является ключевым фактором формирования привычки;
• система XP и стриков воспринимается как реальный мотиватор: трое ре-
спондентов отметили, что появилось ощущение «жалко терять стрик» —
то есть желаемый поведенческий эффект геймификации был достигнут;
62
• запуск приложения непосредственно из Telegram без необходимости уста-
навливать отдельную программу несколько раз был назван решающим
преимуществом по сравнению с аналогами;
• дизайн в едином стиле и тёмная тема «по умолчанию» получили высокие
оценки за визуальную целостность.
К числу замечаний и пожеланий, имеющих практическую ценность для
дальнейшего развития системы, относятся:
• расширение списка предустановленных категорий и возможность созда-
вать пользовательские категории с собственным эмодзи;
• автоматическое распознавание чеков по фотографии (OCR) для ускоре-
ния ввода;
• поддержка нескольких валют и автоматический пересчёт по актуально-
му курсу;
• экспорт операций в формат CSV / Excel для использования во внешних
инструментах;
• уведомления-напоминания через Telegram-бота, если пользователь не
вносил транзакции в течение нескольких дней;
• публикация достижений (новый уровень, завершённая цель) непосред-
ственно в чат пространства — усиление социального контура геймифи-
кации.
Респондент R5, поставивший наиболее низкую оценку мотивационному по-
тенциалу геймификации (3 балла за критерий C3), пояснил, что игровые ме-
ханики его лично не вдохновляют, однако при этом высоко оценил остальные
характеристики приложения. Этот случай иллюстрирует ожидаемое ограни-
чение метода: эффективность геймификации в значительной мере зависит от
индивидуальной восприимчивости пользователя, что соответствует выводам,
изложенным в обзоре литературы (см. главу 1) [6].
63
3.4.6 Анализ результатов тестирования
Совокупность результатов трёх этапов тестирования позволяет сформули-
ровать ряд выводов.
Тестирование API подтвердило корректность реализации серверной логи-
ки на всех проверенных эндпоинтах, включая обработку отрицательных сце-
нариев (неверный hash, истёкший токен, обращение к чужому пространству).
Это даёт основания считать серверный контракт стабильным и пригодным
для дальнейшей интеграции внешних клиентов (например, веб-версии или
альтернативного фронтенда).
Функциональное тестирование на реальных устройствах не выявило кри-
тических дефектов. Все 14 ключевых пользовательских сценариев были
успешно пройдены, расхождений между поведением приложения на iOS и
Android зафиксировано не было.
Пользовательское приёмочное тестирование показало, что приложение об-
ладает высоким уровнем удобства (среднее значение оценки — 4,5 из 5,0) и
положительно воспринимается потенциальной аудиторией: 87,5% респонден-
тов выразили готовность использовать приложение на постоянной основе.
Наиболее высокую оценку получил визуальный дизайн (4,8), наиболее низ-
кую — полезность аналитики (4,3), что указывает на возможное направление
дальнейшей работы: расширение аналитических возможностей и более выра-
зительная визуализация трендов. Результаты опроса в целом подтверждают
исходную гипотезу работы о позитивном влиянии геймификации на форми-
рование привычки регулярного финансового учёта.
Замечания и пожелания, собранные в ходе UAT, представляют собой го-
товый бэклог направлений развития системы и могут стать основой для по-
следующих итераций разработки.
3.4.7 Выявленные проблемы и решения
В ходе разработки и тестирования был выявлен ряд нетривиальных тех-
нических проблем. Таблица 3.5 содержит их описание и принятые решения.
Совокупность выявленных и решённых проблем подтверждает, что при
разработке на платформе Telegram Mini Apps необходимо учитывать специ-
64
Таблица 3.5: Выявленные проблемы и их решения
Проблема Решение
JWT_SECRET читался до загрузки .env: то- Переход на [Link] с
кены подписывались пустым ключом фабричной функцией
BigInt не сериализуется в JSON: ошибка Глобальный патч
при возврате ответа с telegramId [Link] в [Link]
Бесконечный цикл перезапросов при 401: Проверка !isAuthEndpoint в Axios
клиент бесконечно повторял авторизацию interceptor
Конфликт Prisma ESM/CommonJS с ком- Переход на commonjs в tsconfig, вывод
пилятором SWC Prisma в src/generated/
Скролл не работает в fixed-элементах Все формы и пикеры реализованы как от-
Telegram WebApp дельные страницы с роутингом
Онбординг показывался повторно после Оптимистичное обновление стора до вызо-
завершения ва navigate()
Cloudflare Tunnel меняет URL при переза- Обновление URL вручную в BotFather;
пуске план — именованный туннель
фические ограничения WebView-окружения, особенности жизненного цикла
NestJS-модулей и нюансы совместимости инструментария.
65
Заключение
В рамках настоящей выпускной квалификационной работы была разрабо-
тана геймифицированная система управления личными финансами FinQuest
в формате Telegram Mini App. В ходе работы были решены все поставленные
задачи.
1. Анализ предметной области и обзор аналогов. Исследована про-
блема вовлечённости пользователей финансовых приложений: установлено,
что около 80% пользователей прекращают регулярное использование в тече-
ние первого месяца. Проведён сравнительный анализ четырёх приложений —
Monefy, YNAB, CoinKeeper и Дзен-мани. Выявлено, что ни один из рассмот-
ренных продуктов не реализует геймификацию как инструмент удержания и
не работает в формате Telegram Mini App.
2. Обоснование выбора технологий. На основе сравнительного ана-
лиза обоснован выбор технологического стека: React 19 + Vite + Tailwind
CSS v4 + Zustand + TanStack Query v5 для клиентской части; NestJS 11 +
Prisma 7 + PostgreSQL 16 для серверной части. Обоснован выбор Telegram
Mini Apps как платформы: нулевой порог входа, встроенная авторизация,
охват аудитории свыше 900 млн пользователей.
3. Проектирование архитектуры системы. Спроектирована трёх-
звенная клиент-серверная архитектура с инфраструктурным уровнем на ос-
нове Nginx, PM2, Docker и Cloudflare Tunnel. Разработана схема базы данных
из 14 связанных моделей. Спроектированы REST API (более 25 эндпоинтов),
система аутентификации на основе HMAC-SHA256 и JWT, пользовательский
интерфейс и система геймификации с XP-механикой, стриком, челленджами
и сезонной таблицей лидеров.
4 и 5. Реализация клиентской и серверной части. Реализовано пол-
нофункциональное приложение с учётом доходов и расходов, совместными
66
финансовыми пространствами, целями накопления, повторяющимися тран-
закциями, системой геймификации и базой знаний. В ходе реализации вы-
явлен и решён ряд нетривиальных технических проблем, характерных для
платформы Telegram Mini Apps: ограничения скролла в WebView, особен-
ности жизненного цикла NestJS-модулей, несовместимость BigInt с JSON-
сериализацией.
6. Развёртывание и тестирование. Система развёрнута на VPS под
управлением Ubuntu 24.04 и доступна пользователям через Telegram. Прове-
дено тестирование API (11 эндпоинтов) и функциональное тестирование (14
тест-кейсов) — все сценарии успешно пройдены.
Достигнутые результаты. Разработанная система FinQuest закрывает
выявленные пробелы рынка: является единственным в рассмотренном ряду
приложением, сочетающим геймификацию (XP, уровни, стрик, челленджи,
рейтинг) с форматом Telegram Mini App и полностью бесплатным доступом.
Совместные финансовые пространства реализуют социальное измерение фи-
нансового учёта — фактор, полностью отсутствующий у большинства анало-
гов.
Направления дальнейшего развития:
• расширенная аналитика: прогноз расходов, сравнение месяцев, тепловая
карта трат;
• постоянный именованный Cloudflare Tunnel с собственным доменом;
• push-уведомления через Telegram Bot API для напоминаний о стрике и
приближающихся дедлайнах челленджей;
• автоматическое тестирование — unit-тесты сервисов и e2e-тесты ключе-
вых пользовательских сценариев.
Практическая значимость работы состоит в создании готового к использо-
ванию приложения, доступного любому пользователю Telegram без установки
дополнительного программного обеспечения. Разработанные архитектурные
решения и описанные технические подходы могут быть применены при со-
здании других клиент-серверных приложений на платформе Telegram Mini
Apps.
67
Библиографический список
использованной литературы
1. Дзен-мани — приложение для учёта личных финансов : [сайт]. — URL:
[Link] (дата обращения: 10.05.2026). — Текст : электрон-
ный.
2. Финансовая грамотность и финансовое поведение россиян : результаты
всероссийского исследования / Аналитический центр НАФИ : [сайт]. —
URL: [Link] (дата обращения: 10.05.2026). — Текст : электрон-
ный.
3. CoinKeeper — трекер личных финансов : [сайт]. — URL:
[Link] (дата обращения: 10.05.2026). — Текст : элек-
тронный.
4. Deci, E. L. Self-determination theory and the facilitation of intrinsic
motivation, social development, and well-being / E. L. Deci, R. M. Ryan.
— Текст : непосредственный // American Psychologist. — 2000. — Vol. 55,
№ 1. — P. 68–78.
5. Deterding, S. From game design elements to gamefulness: defining
«gamification» / S. Deterding, D. Dixon, R. Khaled, L. Nacke. — Текст : непо-
средственный // Proceedings of the 15th International Academic MindTrek
Conference (MindTrek ’11). — New York : ACM, 2011. — P. 9–15.
6. Hamari, J. Does Gamification Work? — A Literature Review of Empirical
Studies on Gamification / J. Hamari, J. Koivisto, H. Sarsa. — Текст : непо-
средственный // Proceedings of the 47th Hawaii International Conference on
68
System Sciences (HICSS). — Washington : IEEE Computer Society, 2014. —
P. 3025–3034.
7. Jones, M. JSON Web Token (JWT) : RFC 7519 / M. Jones, J. Bradley,
N. Sakimura. — Текст : электронный // Internet Engineering Task Force
(IETF). — 2015. — URL: [Link] (дата обра-
щения: 10.05.2026).
8. Koivisto, J. The rise of motivational information systems: A review of
gamification research / J. Koivisto, J. Hamari. — Текст : непосредствен-
ный // International Journal of Information Management. — 2019. — Vol.
45. — P. 191–210.
9. Monefy — приложение для учёта расходов : [сайт]. — URL:
[Link] (дата обращения: 10.05.2026). — Текст :
электронный.
10. NestJS — A Progressive [Link] Framework : official documentation : [сайт].
— URL: [Link] (дата обращения: 10.05.2026). — Текст : элек-
тронный.
11. PostgreSQL 16 Documentation / PostgreSQL Global Development Group
: [сайт]. — URL: [Link] (дата обращения:
10.05.2026). — Текст : электронный.
12. Prisma — Next-generation [Link] and TypeScript ORM : official
documentation : [сайт]. — URL: [Link] (дата обращения:
10.05.2026). — Текст : электронный.
13. React : official documentation : [сайт]. — URL: [Link] (дата об-
ращения: 10.05.2026). — Текст : электронный.
14. Telegram Mini Apps : official documentation : [сайт]. — URL:
[Link] (дата обращения: 10.05.2026). —
Текст : электронный.
15. You Need A Budget (YNAB) : [сайт]. — URL: [Link] (дата
обращения: 10.05.2026). — Текст : электронный.
69