Skip to main content

Redirect Service

Независимый микросервис сокращения ссылок с Open Graph превью, QR-кодами и аналитикой кликов.

Домен: start.goloot.online Стек: Fastify + Prisma + QRCode


1. Summary

Goal: Обеспечить красивые короткие ссылки для маркетинговых кампаний, реферальной программы и push-уведомлений с поддержкой OG-превью для соцсетей.

User Value:

  • Реферальные ссылки с красивым превью при вставке в мессенджеры и соцсети
  • UTM-ссылки для инфлюенсеров формата start.goloot.online/influencer/hash
  • Push-уведомления с трекингом кликов
  • QR-коды для оффлайн-промо

2. Business Logic

Формат: start.goloot.online/ref_CODE

Поведение:

  1. Проверяет ReferralCode в БД по code (без ref_ префикса)
  2. Если не найден или неактивен -- 404
  3. Инкрементирует clicksCount (fire-and-forget, только для реальных пользователей)
  4. Для social media ботов -- отдаёт HTML с OpenGraph meta-тегами (настройки из GlobalSettings)
  5. Для обычных мобильных браузеров (Safari, Chrome, Telegram browser) -- 302 redirect на https://t.me/... (Universal Link / перехват Telegram browser)
  6. Для in-app браузеров (TikTok, Instagram, Facebook, VK, Snapchat, LINE, WeChat, Twitter/X) -- bridge page с кнопкой https://t.me/... (user gesture запускает Universal Link)
  7. Для desktop -- показывает кастомную hypnotic-redirect страницу с QR-кодом

Destination: t.me/{BOT_USERNAME}/{APP_NAME}?startapp=ref_CODE

OG Settings: Берутся из global_settings (key: referral_opengraph), кешируются на 60 секунд

Core Mechanics

1. Bot Detection

Сервис определяет social media ботов по User-Agent для показа OG-превью вместо редиректа. Поддерживаемые боты:

  • Facebook (facebookexternalhit, facebot)
  • Twitter/X (twitterbot)
  • Telegram (telegrambot)
  • Discord (discordbot)
  • WhatsApp, LinkedIn, Slack, VK, Reddit, Skype, Google, Apple

2. Open Graph Preview

Для ботов генерируется HTML с meta-тегами:

  • og:title, og:description, og:image, og:url
  • twitter:card, twitter:title, twitter:description, twitter:image
  • telegram:channel
  • meta http-equiv="refresh" для автоматического редиректа (1 сек для referral OG, 0.5 сек для UTM OG ботов, 2 сек для preview страниц)
Referral OG Settings

Для реферальных ссылок OG-настройки (title, description, image) берутся из GlobalSettings с ключом referral_opengraph и кешируются in-memory на 60 секунд. Для UTM-ссылок настройки берутся из ShortLink.title/description/imageUrl или из UTMCampaign.

3. Image URL Resolution

Относительные URL картинок преобразуются в полные:

  • Production: static.goloot.online + путь
  • Development: localhost:4000 + путь
  • Referral preview: /images/opengraph/referrals/{filename}
  • UTM preview: /images/opengraph/utm/{filename}
  • Fallback: Дефолтные картинки (default-referral.jpg, default-utm.jpg, og-image.png)

4. QR Code Generation

Эндпоинт /api/qr/:param генерирует PNG QR-код:

  • Содержимое: t.me/{BOT}/{APP}?startapp={param}
  • Размер: 280px, margin: 2, error correction: M
  • Кеширование: Cache-Control: public, max-age=3600

5. Mobile Device Detection

Сервис определяет тип устройства и браузера для двухуровневой стратегии редиректа:

Тип клиентаОпределениеДействие
Social Media BotisSocialMediaBot()OG HTML с meta-тегами
Regular MobiledeviceType === 'mobile' | 'tablet' + !isInAppBrowser()302 redirect на https://t.me/...
In-app BrowserdeviceType === 'mobile' | 'tablet' + isInAppBrowser()Bridge page с https://t.me/... кнопкой
DesktopИначеLanding page + QR

In-app browsers определяются через isInAppBrowser() (redirect-service/src/utils/user-agent.ts) по паттернам User-Agent:

  • TikTok (tiktok, bytedancewebview, musical_ly)
  • Instagram (instagram)
  • Facebook (fban, fbav, fb_iab)
  • VK (vkandroid, vkclient)
  • Snapchat (snapchat)
  • LINE (line/)
  • WeChat (micromessenger)
  • Twitter/X (twitter)
Почему двухуровневая стратегия

Regular mobile (Safari, Chrome, Telegram browser): 302 redirect на https://t.me/... работает корректно — Telegram browser перехватывает навигацию на t.me на уровне приложения, iOS Safari / Android Chrome запускают Universal Link / App Link.

In-app browsers (TikTok, Instagram, Facebook и др.): перехватывают HTTP навигацию и блокируют Universal Links, поэтому 302 redirect на https://t.me/... откроет t.me как веб-страницу. Решение — bridge page, где user gesture на кнопке с https://t.me/... запускает Universal Link.

tg:// scheme (как в button href, так и в auto-JS window.location.href) показывает нативный iOS диалог «Открыть в приложении Telegram?» — используется только в крайнем fallback.

6. Bridge Page

Bridge page используется только для in-app браузеров (TikTok, Instagram, Facebook, VK, Snapchat, LINE, WeChat, Twitter/X):

  1. Показывает кнопку "Открыть в Telegram" с https://t.me/... (Universal Link)
  2. User gesture на Universal Link открывает Telegram напрямую без диалогов подтверждения
  3. Минималистичный дизайн — логотип, подзаголовок, одна кнопка

Обычные мобильные браузеры (Safari, Chrome, Telegram browser) получают прямой 302 redirect на https://t.me/... без bridge page.

7. Click Tracking

Реальные переходы (не от ботов) трекируются:

МаршрутМодельПолеСтратегия
ref_CODEReferralCodeclicksCount (increment)fire-and-forget
influencer/hashUTMCampaignlinkClicksCount (increment)fire-and-forget
push_HASHPushRedirect + PushNotificationclicksCount + clickedCount + PushAnalytics записьawait (блокирует до завершения записи)
linkClicksCount vs clicksCount в UTMCampaign

linkClicksCount — клики по redirect-ссылке (инкрементируется Redirect Service). clicksCount — открытия приложения (app opens, инкрементируется Backend).

Tracking strategies

Referral и UTM: fire-and-forget — ошибка трекинга не блокирует редирект. Потеря одного клика при сбое БД — некритично.

Push: используется await — редирект выполняется только после успешной записи в БД (но ошибка трекинга всё равно перехватывается try/catch и не ломает ответ).

{ increment: 1 } — атомарная операция в PostgreSQL, корректна при конкурентных запросах.

Edge Cases

СитуацияПоведениеHTTP Status
Пустой shortCodeBad Request400
Пустой influencer/hashBad Request400
Ссылка не найденаNot Found404
Реферальный код не найден/неактивенNot Found404
Push redirect не найден/неактивенNot Found404
Ссылка истекла (expiresAt < now)Gone410
Неизвестный формат /:shortCode (не ref_)Not Found + подсказка формата UTM404
Rate limit exceeded (>100 req/min)Rate limit exceeded429
Ошибка БД на health checkDatabase connection failed503
Ошибка OG-настроек для referral ботаFallback: обычный 302 redirect302

3. ADR (Architectural Decisions)

Почему отдельный микросервис?

Проблема: Redirect-сервис должен работать на отдельном домене (start.goloot.online) с минимальной задержкой. Объединение с основным backend добавило бы оверхед от middleware (auth, sessions, etc.) для каждого редиректа.

Решение: Независимый Fastify-сервер с собственным Prisma-клиентом. Общая БД с backend, но изолированный runtime.

Последствия:

  • Минимальная задержка редиректа (нет auth middleware)
  • Независимый деплой и масштабирование
  • Дублирование Prisma Client (отдельный pnpm prisma generate)
  • Общая schema.prisma (изменения в моделях требуют синхронизации)

Почему двухуровневая мобильная стратегия?

Проблема: In-app browsers (TikTok, Instagram, Facebook и др.) перехватывают HTTP навигацию и блокируют Universal Links, поэтому 302 redirect на https://t.me/... открывает t.me как веб-страницу, а не приложение. Обычные мобильные браузеры (Safari, Chrome, Telegram browser) обрабатывают 302 redirect корректно.

Решение: Двухуровневая стратегия на основе isInAppBrowser():

  1. Regular mobile (Safari, Chrome, Telegram browser) — 302 redirect на https://t.me/.... Telegram browser перехватывает навигацию на t.me и открывает Mini App, Safari/Chrome запускают Universal Link.
  2. In-app browser (TikTok, Instagram, Facebook, VK, Snapchat, LINE, WeChat, Twitter/X) — bridge page с кнопкой https://t.me/.... User gesture на кнопке запускает Universal Link.

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

  • Bridge page для всех мобильных — лишний тап для пользователей обычных браузеров, где 302 redirect работает корректно.
  • tg:// scheme — всегда показывает confirmation dialog на iOS.
  • Автоматический window.location.href = tg://... — iOS WKWebView блокирует автопереходы по custom schemes; диалог всё равно появляется в нативном Safari.

Последствия: Оптимальный UX: 0 тапов для regular mobile (прямой редирект), 1 тап для in-app browsers (bridge page). Desktop → hypnotic landing page с QR.

Почему монолитный server.ts?

Проблема: Сервис имеет ~14 роутов, но все следуют одному паттерну: найти в БД, проверить, отдать HTML/redirect.

Решение: Все роуты в одном файле server.ts (~1710 строк). Утилиты вынесены в constants/ и utils/templates.ts.

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

  • Domain-driven разделение -- избыточно для сервиса с одной ответственностью
  • Controller/Service layers -- YAGNI, нет сложной бизнес-логики

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

Dual-source lookup для UTM

Проблема: UTM-ссылки могут существовать как в short_links (legacy), так и в utm_campaigns (новая архитектура).

Решение: Каскадный поиск: сначала short_links, затем fallback в utm_campaigns. Из utm_campaigns создаётся mock ShortLink объект для унификации обработки.

Path-based shortCode conversion

URL формата influencer/hash конвертируется в influencer_hash через underscore для поиска в БД. Это правило жёстко зашито в логику сервиса.


4. Architecture

Services Overview

Key Components

КомпонентПутьОписание
Serverredirect-service/src/server.tsВсе роуты, утилиты, запуск сервера
Environment Configredirect-service/src/config/environment.config.tsКонфигурация ENV (fail-fast валидация)
Constantsredirect-service/src/constants/6 файлов: network, security, template, urls, paths, index
Templatesredirect-service/src/utils/templates.tsРендеринг HTML шаблонов с переменными (generateHypnoticHTML, generateBridgeHTML)
Hypnotic Templateredirect-service/src/templates/hypnotic-redirect.htmlDesktop страница с QR-кодом и кнопкой "Открыть напрямую"
Bridge Templateredirect-service/src/templates/bridge-redirect.htmlMobile bridge page с одной кнопкой https://t.me/...

Configuration

Environment Variables
ПеременнаяОбязательнаОписание
NODE_ENVДаОкружение (development / production)
PORTДаПорт сервера
HOSTДаХост сервера
LOG_LEVELДаУровень логирования
DATABASE_URLДаPostgreSQL connection string
STATIC_URLДаURL статического контента (например https://static.goloot.online)
START_URLДаURL сервиса (например https://start.goloot.online)
TELEGRAM_BOT_USERNAMEДаUsername Telegram бота
TELEGRAM_APP_NAMEДаИмя Telegram Mini App

Все переменные валидируются при старте (fail-fast). Отсутствие любой -- crash.

Security & Rate Limiting
ПараметрЗначениеОписание
Rate Limit100 req / 1 minPer-IP ограничение
CORSorigin: trueВсе домены (необходимо для редиректов)
Helmet CSPfalseОтключён (для HTML-страниц с inline styles)
Cache (QR)max-age=36001 час кеширования
Cache (Redirects)no-cache, no-store, must-revalidateБез кеширования

5. Database Schema

Models

МодельОписаниеКлючевые поля
ShortLinkКороткая ссылка для UTM кампанииshortCode, destinationUrl, title, description, imageUrl, isActive, expiresAt
UTMCampaignМаркетинговая кампания (fallback source)shortCode, botLink, source, medium, campaign, influencer, clicksCount (app opens), linkClicksCount (redirect clicks), conversions
ReferralCodeРеферальный код пользователяcode, isActive, userId, clicksCount
PushRedirectRedirect для push-уведомленияshortCode, pushId, originalUrl, isActive, clicksCount
PushNotificationPush-уведомление (для трекинга кликов)clickedCount, sentCount
PushAnalyticsАналитика кликов по pushpushId, action, userAgent, ipAddress
GlobalSettingsГлобальные настройки (OG для referrals)key, ogReferralTitle, ogReferralDescription, ogReferralImage

Relationships

Read-only access

Redirect Service использует БД в read-only режиме для всех таблиц, кроме:

  • ReferralCode.clicksCount (инкремент при переходе по ref_ ссылке)
  • UTMCampaign.linkClicksCount (инкремент при переходе по UTM redirect-ссылке)
  • PushRedirect.clicksCount (инкремент при клике по push)
  • PushNotification.clickedCount (инкремент при клике по push)
  • PushAnalytics (создание записи при клике по push)

6. API Endpoints

МетодЭндпоинтОписание
GET/Service info (name, version, endpoints)
GET/healthHealth check (проверка подключения к БД)
GET/metricsPrometheus metrics
GET/:shortCodeОсновной redirect (только ref_CODE формат)
GET/:influencer/:hashPath-based UTM redirect (influencer/hash -> influencer_hash)
GET/push_:hashPush notification redirect с трекингом клика
GET/:brand/push_:hashBranded push redirect с трекингом клика
Подробности роутов

GET /:shortCode -- обрабатывает только ссылки с префиксом ref_. Другие форматы возвращают 404 с подсказкой использовать influencer/hash.

GET /:influencer/:hash -- основной роут для UTM-ссылок. Конвертирует path в underscore shortCode. Двойной lookup: short_links -> utm_campaigns.

GET /preview/:influencer/:hash -- специальная обработка для r/hash (referral preview). Если influencer === 'r', то hash трактуется как referral code.

GET /stats/:influencer/:hash -- аналогично preview, если influencer === 'r', возвращает статистику реферального кода.

Push stats -- включают CTR (clickRate) = clickedCount / sentCount * 100.


  • UTM Tracking -- маркетинговые кампании и UTM-параметры
  • Referrals -- реферальная система и коды
  • Push Notifications -- push-уведомления и PushRedirect
  • Static Nginx -- раздача статических файлов (OG-картинки)