проекти/vrachka
Уеб приложение·2024· 6 седмици·собствен продукт на Level 8

Vrachka.eu

AI астрология платформа с персонализирани хороскопи

Next.jsOpenAIStripePostgreSQL
Посети сайта
01 / 02
Home — hero с анимирани звезди
level8
Home — hero с анимирани звезди
Daily horoscope — всичките 12 зодии
Home — hero с анимирани звезди
2 000+
Активни потребители
100+ DB миграции
DB миграции
200+ API маршрута
API маршрути
Десетки cron задачи
Планирани задачи
──── 01// ПРЕДИЗВИКАТЕЛСТВОТО

Предизвикателството

Vrachka.eu е собствен продукт на Level 8. Ние сме и идеята, и разработчиците, и операторите — което значи, че всеки ред код и всяко взето решение носим сами.

Тръгнахме от празна страница. Нямаше прототип, нямаше легаси код, нямаше маркетинг бюджет. Само хипотеза — че AI и класическата астрология могат да се съчетаят по начин, който хората в България още не бяха видели.

Целта не беше просто сайт. Искахме работещ продукт — с плащания, абонаменти, ежедневно AI съдържание на два езика и математическо ядро, което смята натални карти в реално време. Всичко това трябваше да е готово за платени клиенти от първия ден.

──── 02// РЕШЕНИЕТО

Решението

Изградихме Vrachka като една завършена платформа — не сбор от отделни части. AI генерира персонализирани прогнози, астрономическото ядро смята класически карти, плащанията се случват на едно място, а потребителският профил помни всичко.

Компромисите бяха съзнателни: избрахме модерни технологии вместо модни, автоматизирахме всеки процес, който иначе би изял време, и построихме fallback-и за всяка критична точка. Когато един AI модел пропада — идва вторият, после третият. Когато една крон задача изтече — преminava към durable workflow. Всичко, което може да счупи нещо за платен клиент, има защита.

Резултатът: работеща платформа, която ние използваме всеки ден за собствения си бизнес. Което значи, че знаем какво точно означава да поддържаш продукт в production — не само да го построиш.

[+]AI хороскопи и таро четения на български и английски
[+]Онлайн плащания и абонаментни планове
[+]Натални карти с професионална астрономическа точност
[+]Потребителски профили с история и препоръки
[+]Автоматична дневна/седмична/месечна генерация през scheduled jobs
[+]Responsive дизайн, оптимизиран за mobile и PWA
──── 03// АРХИТЕКТУРА

Как е подредено отвътре

Vrachka е една платформа с няколко лица — абонаменти, еднократни платени продукти, онлайн магазин, съдържателен блог, AI инструменти и астрономическо ядро за натални карти. Вместо да разбиваме това на отделни услуги, го построихме в един продукт върху модерна cloud инфраструктура. Един deploy, един код, една база данни — по-малко точки на провал, по-бърза работа.

ACTORFRONTENDBACKENDDATABASEEXTERNALHTTPSезик + authserver fetchизчисленияAI заявкаcheckoutwebhookemaildurablebatchПотребител (BG / EN)Edge middlewareNext.js 16 приложениеSupabase (DB + Auth)AI gatewayStripe (плащания)Resend (email)Астрономическо ядроScheduled jobsDurable workflows
Потоци на данните
01

Регистрация → потвърждение по email → влизане. Magic link идва през наш собствен транзакционен поток, а middleware гарантира, че нерегистриран потребител не вижда защитени секции.

02

Натална карта (безплатна точка на първо докосване) — профилът дава датата на раждане, астрономическото ядро смята позициите на планетите, AI пише интерпретацията, резултатът се запазва в историята.

03

Платен продукт — валидираме заявката, пренасочваме към Stripe, получаваме webhook, записваме покупката идемпотентно, пращаме квитанция. Ако нещо се обърка — retry логиката си го връща.

04

Ежедневно AI съдържание — scheduled job стартира durable workflow, всяка зодия е отделна стъпка, която може да се опитва отново независимо. При пик на грешки един знак не събаря останалите.

05

Redirects — комбинация от статични правила и динамични от админ панела, с кеш в паметта, за да не удря базата на всяка заявка.

──── 04// ТЕХНИЧЕСКИ ИЗБОРИ

Защо точно тези технологии

Всеки избор има цена. Показваме какво избрахме, от какво се отказахме и защо.

Кой AI да задвижва съдържанието?

Избор
AI gateway с три модела в каскада
Отхвърлени
Директна връзка само с един доставчик, Single-model решение
Защо
Българският език е труден за по-старите модели — трябваше ни такъв, който пише естествено на кирилица. Избрахме подход с gateway, в който един и същ код може да превключва между различни AI доставчици без промяна в логиката. Всеки критичен feature има главен модел и два fallback-а — ако нещо се случи с единия, следващият поема.
Цена
AI моделите се развиват бързо — това, което работи днес, може да е различно след месец. Затова построихме инфраструктурата с модули, които се сменят с една редакция. Плюс проследяваме цената на всяка заявка.

Астрономическите изчисления — вътре в приложението или на отделен сървър?

Избор
Вградено астрономическо ядро в същото приложение
Отхвърлени
Отделен сървър за астрономически изчисления (Python), Чисто JavaScript библиотеки (недостатъчно точни за професионални карти)
Защо
Всичко в едно място значи по-малко движещи се части, няма мрежови забавяния между сървъри, един deploy, един мониторинг. Цената — по-бавен първи старт — е приемлива за продукт, който не е real-time игра.
Цена
Астрономическите данни тежат около 20 MB и трябва да пътуват с функцията, която ги ползва. Решихме го с конфигурация, която включва тези файлове само в маршрутите, където реално трябват.

Облачна база данни или самостоятелен сървър?

Избор
Облачен Postgres с вградена защита на ниво ред
Отхвърлени
Самостоятелен Postgres + отделен сървис за автентикация, Комбинация от няколко доставчика
Защо
Сигурността на потребителските данни е зашита на ниво база — дори ако случайно пропуснем проверка в кода, базата не връща чужди данни. Плюс база, автентикация, файлово хранилище и realtime идват в един пакет — по-малко оперативна сложност.
Цена
Приемаме зависимост към конкретен доставчик за слоя автентикация. Ако някога се наложи миграция, това е сериозна работа. За текущия мащаб печалбата от бързо разработване и вградена сигурност надделява над риска.

Тежки ежедневни задачи — как?

Избор
Durable workflows с независими стъпки
Отхвърлени
Класическа опашка с retry, Отделна инфраструктура за фонова обработка
Защо
Всяка сутрин трябваше да се генерират десетки AI прогнози за минута. Когато една пропадне — не искаш цялата партида да се рестартира. Решението позволява всеки елемент да се опитва отново самостоятелно, без да влияе на останалите.
Цена
Технологията е нова — държим и старата версия като резерва за известно време. Проблемите изискват четене на специфични логове.

Как пречистваме AI-генерирано HTML съдържание?

Избор
Чист JavaScript sanitizer без DOM зависимости
Отхвърлени
Браузърен sanitizer с jsdom, Собствена имплементация
Защо
Избраният вариант работи навсякъде — в сървъра, в edge, в browser — без да изисква виртуална браузерна среда. Поддържа whitelist за вградени YouTube и Spotify плеъри, за да не се счупят блог постовете.
Цена
Не запазва HTML коментари. Това ни струваше един неприятен урок (виж 'Обрати'). Оттогава всички бизнес маркери в съдържанието са с data атрибути, не коментари.
──── 05// ТЕХНОЛОГИИ

Технологии

>Frontend
  • Next.js (App Router)
  • React
  • TypeScript
  • Tailwind CSS
  • Radix UI за интерактивни елементи
  • Анимации и 3D визуализации
  • Многоезичност (BG / EN)
>Backend
  • Next.js API routes
  • Durable workflows за тежки задачи
  • Edge middleware (език, auth, пренасочвания)
  • Валидация на всяка заявка
>База данни
  • Облачен Postgres (Supabase)
  • Защита на ниво ред за потребителски данни
  • Realtime обновления
  • Контролирани миграции
>Автентикация
  • Email потвърждение през собствена поща
  • Google OAuth
  • Onboarding gate-ове в middleware
>Плащания
  • Stripe Checkout (абонаменти + еднократни)
  • Идемпотентен webhook handler
  • Защита при edge cases в retry логиката
>AI
  • AI gateway с няколко доставчика
  • Различни модели за различни задачи
  • Декларативна fallback каскада
  • Tracking на разхода per заявка
>Астрономическо ядро
  • WASM ядро за професионална точност
  • Lightweight fallback за бързи изчисления
  • Собствени модули: solar return, profections, transits, synastry
>Поща
  • Транзакционни имейли (Resend)
  • React Email шаблони
  • IMAP четец в админ панела
>Инфраструктура
  • Vercel (hosting + crons + edge)
  • Cloudflare DNS
  • PWA с offline поддръжка
>Мониторинг
  • Vercel Analytics + Speed Insights
  • Microsoft Clarity (session recordings)
  • Google Analytics + Search Console
  • Собствен health-alerts cron
>Тестове
  • Unit + integration тестове
  • End-to-end + visual тестове
  • Strict TypeScript + lint
──── 06// ОТ КОДА

Парчета от кухнята

Избрани откъси от кода с пояснение защо е написан така. За техническите хора в залата.

billing / period helper
01/04

Помощна функция за Stripe — устойчива към промени в API

Когато Stripe преместиха критично поле, нашето решение беше една функция, която познава и стария, и новия формат. Правило: никой не чете полето директно, всички минават през нея. Ако Stripe пак промени нещо — редакцията е на едно място.

// Single source of truth for Stripe period fields.
// Handles both pre-clover (root) and post-clover (item) shapes.
export function getSubscriptionPeriod(
  sub: Stripe.Subscription
): { start: number; end: number } {
  const item = sub.items?.data?.[0] as
    | (Stripe.SubscriptionItem & {
        current_period_start?: number;
        current_period_end?: number;
      })
    | undefined;
  const rootLike = sub as unknown as {
    current_period_start?: number;
    current_period_end?: number;
  };
  const start = item?.current_period_start ?? rootLike.current_period_start;
  const end = item?.current_period_end ?? rootLike.current_period_end;
  if (typeof start !== 'number' || typeof end !== 'number') {
    throw new Error(`Subscription ${sub.id} missing current_period fields`);
  }
  return { start, end };
}
ai-workflow / generation runner
02/04

Ежедневна AI генерация като независими стъпки

Десетки AI заявки наведнъж не могат да се правят в една функция — удрят лимита за време. Разбивката на durable стъпки означава, че всяка зодия е отделна, самостоятелна единица. Ако пропадне — опитва се отново. Останалите не страдат.

'use workflow';

import { generateForSign } from './generate-for-sign';
import { getDailyEphemeris } from '@/lib/astrology/ephemeris-astronomy-engine';

const ZODIAC_SIGNS = [
  'aries', 'taurus', 'gemini', 'cancer', 'leo', 'virgo',
  'libra', 'scorpio', 'sagittarius', 'capricorn', 'aquarius', 'pisces',
];

export async function generateHoroscopesWorkflow() {
  const now = new Date();
  const languages: Array<'bg' | 'en'> = ['bg', 'en'];
  const periods = ['daily'];
  if (now.getDay() === 1) periods.push('weekly');
  if (now.getDate() === 1) periods.push('monthly');

  const ephemeris = getDailyEphemeris(now);

  for (const period of periods) {
    for (const sign of ZODIAC_SIGNS) {
      // Each call is a durable step. Retries independently.
      await generateForSign({ sign, period, languages, ephemeris });
    }
  }
}
edge middleware / redirects
03/04

Пренасочвания от базата с кеш в паметта

Имахме два вида правила — фиксирани в кода и управлявани от админ панела. Заявка към базата на всяко зареждане на страница би удвоила времето за отговор. Решението: зарежда всичко веднъж и пази в паметта за кратко. При грешка в базата — сервира стария кеш, за да няма downtime.

// 5-min TTL cache; stale fallback on DB error (zero downtime).
let redirectsCache: Map<string, {
  destination: string;
  statusCode: number;
}> | null = null;
let redirectsCacheTime = 0;
const CACHE_TTL = 5 * 60 * 1000;

async function getRedirects() {
  const now = Date.now();
  if (redirectsCache && now - redirectsCacheTime < CACHE_TTL) {
    return redirectsCache;
  }
  try {
    const { data } = await supabase
      .from('redirects')
      .select('source_path, destination_path, status_code')
      .eq('is_active', true);
    const fresh = new Map();
    for (const r of data || []) {
      fresh.set(r.source_path, {
        destination: r.destination_path,
        statusCode: r.status_code,
      });
    }
    redirectsCache = fresh;
    redirectsCacheTime = now;
    return fresh;
  } catch {
    return redirectsCache || new Map(); // stale but not broken
  }
}
ai / model router
04/04

Един централен map кой AI модел използва коя функция

Различните функции имат различни нужди — някои искат качество, други скорост, трети цена. Тази карта централизира решенията, така че промяна на модел е редакция на един ред, не обхождане на целия код. AI пейзажът се движи бързо — тази абстракция ни пази.

// Декларативна карта: функция → [основен, резервен 1, резервен 2].
// Кодът пише callAI({ feature: 'daily_short' }) — без да знае моделите.
export const FEATURE_MODEL_MAP: Record<AIFeature, string[]> = {
  daily_short:  ['gemini_flash_lite', 'gemini_flash',    'deepseek'],
  long_form:    ['gemini_pro',        'gemini_flash',    'deepseek'],
  // ... още функции, routing layer-ът се грижи за останалото
};
← swipe →
──── 07// ИСТОРИИ ОТ PRODUCTION

Какво се счупи и как го оправихме

Stripe промени API-то си и тихомълком счупи плащанията

Проблем

Един ден започнахме да получаваме странни грешки в логовете. Стари плащания минаваха нормално, нови се блокираха. Оказа се, че Stripe са преместили ключово поле на друго място в обекта, без да нарушат формално документацията. Старите тестови плащания работеха, новите истински — не.

Решение

Написахме помощна функция, която познава и стария, и новия формат. Никъде в кода вече не четем това поле директно. Добавихме и втора защита — ако някое плащане се заcеля между състояния, system-ът го разпознава и го обработва отново. Решено за един ден, без загубени поръчки.

Ежедневната AI генерация удряше лимита за време на сървъра

Проблем

Всяка сутрин платформата трябва да генерира прогнози за всички зодии на два езика. Понеделник сутрин (когато се добавя и седмичната) бюджетът от време не стигаше. Ако един от AI доставчиците забави отговор — цялата партида рестартираше.

Решение

Разбихме процеса на независими стъпки. Всяка зодия се обработва самостоятелно и може да се опита отново, без да влияе на останалите. Нула timeout грешки през последните седмици, 40% по-бързо възстановяване при забавяне на AI.

Тежките астрономически файлове не стигаха до production

Проблем

Локално всичко работеше. В production една от страниците хвърляше грешка за липсващ файл. Причината: build процесът не разпознаваше, че специфични тежки файлове за астрономически изчисления трябва да се носят заедно с тази функция.

Решение

Изрично казахме на build-а кои файлове са нужни за кои маршрути. Първото отваряне стана малко по-бавно, но сметките са точни — което е важното за платен продукт.

Пренасочванията не можеха да удрят базата на всяка заявка

Проблем

Имахме два вида пренасочвания — статични в кода и динамични, които админът добавя. Всяко зареждане на страница би означавало заявка към базата — което би удвоило времето за отговор.

Решение

Кеш в паметта с кратък живот. Първата заявка зарежда всичко наведнъж, следващите четат от паметта. Ако базата падне за кратко — показваме стария кеш, без потребителят да усети нещо.

Грешка в интерпретация на numerology matrix

Проблем

Една налична библиотека подреждаше матрицата на класическата питагорова школа по грешен начин. Редовете и колоните бяха разменени — което значи, че потребителите четяха интерпретации от грешните клетки.

Решение

Пренаписахме помощния модул с правилно подредена матрица. UI компонентите вече използват правилния ред на обхождане. Безопасно, защото матрицата се пре-изчислява от дата на раждане на всяко отваряне — не се пази в базата.

level8 consult --request

Искате ли такъв анализ за вашия продукт?

Показахме какво научихме от Vrachka.eu. Ако искате да прегледаме архитектурата, производителността и разходите за AI на вашия продукт — оставете имейл. Безплатен 10-точков анализ, без ангажимент.

$

Отговаряме в рамките на 24ч. Без спам.

──── 08// ПРОИЗВОДИТЕЛНОСТ

Реални числа, не маркетинг

Честно: mobile performance е активна работа. Тръгнахме от 37, сега сме на 55+ и продължаваме. Правим компромиси, защото рекламите са важни за бизнес модела — не ги отлагаме. Но всичко, което може да се оптимизира без да засяга приходите, минава през шлифовка.

55
performance
92
accessibility
88
Best Practices
98
seo
Основни метрики
lcp
~2.4s (mobile 4G)
inp
~200ms
cls
<0.1
Размер на JavaScript
първо зареждане
~180 KB
най-тежка страница
~250 KB

Живи метрики

Приблизителен снимков кадър. Точните бизнес числа са под NDA.
routes
200+ API маршрута
migrations
100+ DB миграции
cron jobs
Десетки cron задачи
paid products
Няколко платени продукта + абонаменти
i18n
2 езика (BG + EN)
daily a i generations
Стотици AI генерации дневно
growth
Растящи приходи (NDA)
──── 09// ОБРАТИ

Уроци за вашия проект

Всеки обрат тук вече няма да се случи във вашия продукт, ако работите с нас.

01

Пазете бизнес маркерите в съдържанието като данни, не като коментари

Използвахме HTML коментари, за да маркираме специални секции в блог постовете — работеше чудесно. Докато не смени sanitizer-ът поради несъвместимост с новата версия на framework-а. Новият му чисти коментарите по подразбиране. За една нощ всички публикувани статии загубиха маркировките.

Какво бихме направили иначе

Ако ви правим продукт с AI съдържание — ще използваме данни в атрибути, не коментари. Sanitizer-ите се сменят, правилата им се променят. Структурата на съдържанието трябва да е устойчива на тези промени.

02

Тествайте цената на AI модел преди да го пуснете в production

Един AI модел се рекламираше като евтин вариант. На практика се оказа, че харчи токени за 'вътрешно мислене', което потребителят не вижда — на всеки кратък отговор истинският разход беше няколко пъти над очакваното. Бюджетът изгаряше за дни, не за месец.

Какво бихме направили иначе

Преди да пуснем какъвто и да е нов модел в production — тестваме съотношението вход/изход в реални условия. Сменихме го с подходящ вариант и разходите паднаха чувствително, без загуба на качество.

03

Webhook-ите за плащания трябва да оцеляват всякакъв сценарий

Първата ни версия разчиташе на един флаг в базата. Ако нещо се обърка между приемането и записването, retry-ите се въртяха в кръг и блокираха. В единични случаи клиент плащаше, но достъпът му се забавяше. Дори един такъв случай е достатъчен, за да загубиш доверие.

Какво бихме направили иначе

Сега системата разпознава 'заседнали' плащания и ги обработва отново. Имаме и втора защита, която минава през различен път — ако webhook-ът пропусне нещо, основният поток пак довежда плащането до край.

04

Нова комбинация от технологии = неочаквани взаимодействия

Миграция към нова версия на няколко ключови компонента наведнъж разкри, че старите стилове остават кеширани при принудително презареждане на страницата. Няколко секунди потребителите виждаха стар дизайн. Дребно, но забележимо.

Какво бихме направили иначе

Когато смесвате няколко нови технологии наведнъж — проверявайте всяко визуално взаимодействие. Оправихме го с конфигурация, която не кешира определени файлове и рестартира кеширащия слой при нова версия.

──── 10// РЕЗУЛТАТИ

Резултати

Платформата привлече хиляди потребители и се утвърди като уникален продукт на българския пазар за AI-базирана астрология.

2 000+
Активни потребители
15 000+
Генерирани хороскопи месечно
4.8/5
Потребителска оценка
12%
Конверсия безплатен → премиум
Имах идея за AI платформа, но не знаех откъде да започна. Екипът на Level 8 изгради всичко от нулата — от дизайна до AI интеграцията. Сега имаме над 2 000 активни потребители и растем всеки ден."
Д

Десислава Петрова

Основател, Vrachka.eu

// следваща стъпка

Искате продукт, който работи като този?

Всичко, което виждате тук, ние оперираме сами. Същият подход прилагаме и за клиентски проекти. 30 минути безплатен разговор — без ангажимент.