Skip to main content

Banners

1. Summary

Goal: Отображение рекламных баннеров на главной странице в формате слайдера и отслеживание взаимодействий пользователей (просмотры, клики).

User Value: Пользователь видит несколько актуальных рекламных предложений с удобной навигацией. Платформа получает детальную аналитику эффективности каждого баннера.


2. Business Logic

Баннеры отображаются на главной в формате слайдера (до 5 штук), если выполнены условия:

  1. isActive: true — баннер активирован админом
  2. startDate ≤ now OR startDate = null — кампания началась
  3. endDate ≥ now OR endDate = null — кампания не закончилась
  4. Сортировка по createdAt DESC — новые баннеры показываются первыми

Функции слайдера:

ФункцияЗначениеОписание
Авто-ротация15 секАвтоматическая смена слайда
Анимация300msПлавный fade-переход
ИндикаторыDotsТочки для навигации внизу
Лимит5Максимум баннеров в слайдере
Планирование кампаний

Установите startDate и endDate для автоматического показа в нужный период без ручного вмешательства. Несколько активных баннеров ротируются автоматически.

Interaction Tracking

VIEW: Трекается для каждого слайда при его показе (при авто-ротации или ручном переключении):

  1. Слайд становится активным → проверка дедупликации
  2. Если прошло достаточно времени → создаёт запись в BannerAnalytics
  3. VIEW трекается один раз за сессию для каждого баннера

CLICK: Трекается при клике на текущий активный баннер:

  1. Проверяет дедупликацию (cooldown 60 мин)
  2. Открывает ссылку в новой вкладке
  3. Записывает клик при переходе на внешний ресурс

Deduplication

Временные окна дедупликации
ДействиеCooldownПричина
VIEW15 минутЗащита от накрутки при обновлении страницы
CLICK60 минутЗащита от многократных кликов

Алгоритм: Поиск последней записи в BannerAnalytics с тем же userId, bannerId, action в пределах временного окна.

Protection

ДействиеRate LimitAuthValidation
Получить баннерcontent (200/min)Telegram
Записать взаимодействиеmutations (5/min)Telegramaction: view | click
Детали реализации

См. Security Matrix для полного обзора защит.

Edge Cases

СитуацияПоведениеКод
✅ Нет активных баннеров200 OK, data: []
❌ Баннер не найден200 OK, recorded: falseBANNER_NOT_FOUND
⏱️ Повторный просмотр < 15 мин200 OK, recorded: falseDUPLICATE_VIEW_WITHIN_15MIN
⏱️ Повторный клик < 1 час200 OK, recorded: falseDUPLICATE_CLICK_WITHIN_1HOUR
🔒 Пользователь не авторизован200 OK, recorded: falseUSER_NOT_AUTHENTICATED
🎯 Скрытие баннеровСкрыты на 10 минLocalStorage
Fail-Open подход

При ошибке БД во время проверки дедупликации — действие разрешается. Это предотвращает блокировку пользователя из-за технических проблем.


3. ADR (Architectural Decisions)

Почему дедупликация на уровне сервиса, а не БД?

Проблема: Нужно предотвратить накрутку статистики при многократных просмотрах/кликах.

Решение: Сервис BannerDeduplicationService проверяет последнее событие в БД перед записью нового.

Альтернативы (отклонены):

  • Unique constraint — не подходит, т.к. один пользователь может взаимодействовать многократно, но с интервалом
  • Redis rate limiting — избыточно для текущих объёмов, добавляет инфраструктурную сложность
  • Frontend throttling — легко обходится, не защищает от злоумышленников

Последствия: Простое решение на уровне сервиса, легко тестируется и модифицируется.

Почему возвращать 200 OK даже при отклонении?

Проблема: Отклонённые дублирующие запросы — это нормальное поведение, не ошибка.

Решение: Возвращаем { success: true, recorded: false, reason: '...' } вместо 4xx.

Последствия: Фронтенд не показывает ошибку пользователю, но может использовать reason для аналитики.

Почему удалён BannerType enum?

Проблема: В референсном проекте goBind баннеры показывались на разных страницах (BIND, FPS, PRESET). В goloot баннер только на Home.

Решение: Удалить enum BannerType и поле type из модели. Один активный баннер выбирается по createdAt DESC.

Альтернативы (отклонены):

  • Оставить для будущего использования — нарушает YAGNI, создаёт мёртвый код
  • Мягкое удаление — половинчатое решение, UI всё равно показывал бы пустые значения

Последствия: Упрощение схемы и админки, удалены ~200 строк type-related кода из admin.


4. Architecture

Flow: Record Interaction

Key Components

Backend:

КомпонентПутьОписание
BannerServicebackend/src/domains/banners/services/banner.service.tsОсновная логика, getActiveBanners()
BannerDeduplicationServicebackend/src/domains/banners/services/banner-deduplication.service.tsПроверка дублей
Routesbackend/src/domains/banners/routes/user-banners.routes.tsUser API
Schemasbackend/src/domains/banners/schemas/*.schemas.tsВалидация

Frontend:

КомпонентПутьОписание
BannerSliderfrontend/src/components/ui/BannerSlider.tsxСлайдер с авто-ротацией
useBannersfrontend/src/hooks/useBanners.tsХук для получения баннеров
Configfrontend/src/config/banner.config.tsКонстанты слайдера
Admin API

CRUD операции с баннерами находятся в отдельном домене: backend/src/domains/admin/routes/banners.ts


5. Database Schema

Models

МодельОписаниеКлючевые поля
AdBannerКонфигурация баннераtitle, imageUrl, link, isActive, startDate, endDate
BannerAnalyticsСтатистика взаимодействийbannerId, userId, action, createdAt

Relationships

Indexes

ИндексПоляНазначение
banner_user_action_idxbannerId, userId, actionДедупликация
banner_time_idxbannerId, createdAtХронология событий

6. API Endpoints

МетодЭндпоинтОписаниеDocs
GET/api/banners/homeПолучить активные баннеры (массив до 5)
POST/api/banners/:id/:actionЗаписать просмотр/клик
Bootstrap Integration

Баннеры также доступны через Bootstrap API (/api/bootstrap) в поле banners[]. Это позволяет загружать данные одним запросом при старте приложения.


  • Promo Codes — другой канал маркетинговых активностей
  • UTM Tracking — отслеживание источников трафика
  • Notifications — пуш-уведомления