Проблема
У аквариумистов есть общий ритуал. Проснулся, проверил рыб, потестил воду, загуглил “почему у неона побледнел бок”, открыл три форума, нашёл противоречивые советы, закрыл в отчаянии.
Информация размазана по десяткам источников. Половина устарела. Русскоязычных ресурсов, которые объединяли бы энциклопедию, торговлю и сообщество, почти нет. Есть форумы на phpBB из 2010-х, есть группы в VK, есть Avito. Единой штуки нет.
Я решил написать инструмент для себя. Потом подумал, что раз уж пишу, можно сразу для всех.
Что умеет платформа
Best Corals (best-corals.ru) закрывает несколько задач, которые раньше решались тремя приложениями, блокнотом и группой в Telegram.
Дневник аквариума. Добавляешь свой аквариум, указываешь тип, объём и оборудование. Записываешь параметры воды. Платформа строит графики, напоминает о подменах и ругается, когда pH уехал. Есть быстрый ввод: одна строка, Tab для перехода, Enter для сохранения. Когда стоишь у аквариума с мокрыми руками, это удобнее, чем заполнять формы.
Справочник видов. 117 видов: рыбы, кораллы, беспозвоночные. Добавляешь обитателей в виртуальный танк, система показывает, кто с кем уживётся. Если вида нет, можно отправить заявку, и AI заполнит характеристики. Работает не идеально, но быстрее, чем вручную.
AI-ветеринар. Загружаешь фото больной рыбы, описываешь симптомы. Mistral Pixtral Large смотрит на картинку и предлагает возможные диагнозы с рекомендациями. Это не замена ветеринару. Но когда в два часа ночи ты видишь белые точки на плавниках, хочется хотя бы понять, насколько это срочно.
Маркетплейс и аукционы. Продаёшь кораллы, рыб, оборудование. AI генерирует описания. На аукционах есть анти-снайп: ставка в последние 5 минут продлевает время. Об этом подробнее в разделе про безопасность, потому что именно тут я провёл больше всего времени на RLS-политики.
Форум. Обычный форум с категориями. Необычная часть: AI-бот отвечает на вопросы в профильных разделах и умеет суммаризировать длинные обсуждения. Полезно, когда тема на 40 постов скатилась в холивар, а тебе нужен только ответ.
Энциклопедия болезней и автостопщиков. 28 заболеваний, 28 “автостопщиков” (это существа, которые приезжают на живых камнях). Некоторые безобидные, некоторые жрут кораллы. AI может определить автостопщика по фото.
Социальная лента и геймификация. Лента событий сообщества с фильтрами. XP-система за активность, 10 уровней от “Новичка” до “Титана”, лидерборд. Об этом отдельно ниже.
IoT. Через MQTT-брокер контроллер аквариума может отправлять данные прямо в дневник. Пока только Noopsyche, но протокол открытый.
Стек
Когда пишешь pet-project, хочется попробовать что-то новое. Когда он начинает расти, хочется, чтобы оно просто работало.
Слой | Технология | Зачем |
|---|---|---|
Frontend | React 19, Vite 6 | HMR за 50мс. SSR для нишевого проекта избыточен |
Стили | Tailwind CSS 4 | Dark mode из коробки |
Анимации | Motion (ex Framer Motion) | Пружинные анимации для UI |
Backend | Express | Один файл, потом модули. 5 AI-хендлеров, уведомления, Telegram-бот |
БД | Self-hosted Supabase, PostgreSQL 15 | RLS, Auth, Storage, REST API. 93 таблицы, 122 RPC |
AI | Mistral API | В 3-5 раз дешевле OpenAI. 8 фич на бесплатном тарифе |
IoT | Mosquitto MQTT | Стандартный протокол, лёгкий |
Мониторинг | Prometheus, Grafana | 16 алертов |
Деплой | VDS, Docker Compose | ~$15/мес за всё |
Self-hosted Supabase вместо Firebase
Начинал на Firebase. Удобно, быстро, но:
Firestore не PostgreSQL. Нет JOIN-ов, нет нормальных транзакций. Для маркетплейса с заказами это больно. Стоимость растёт непредсказуемо. И данные в облаке Google для нишевого русского проекта казались лишним риском.
Supabase дал мне PostgreSQL с RLS, нормальный Auth, Storage для картинок. Всё self-hosted на моём VDS в Docker.
Переезд стоил мне две недели и dbMapper.ts на 200 строк, который конвертирует snake_case в camelCase и обратно. Каждый день я писал маппинг для очередной таблицы. Каждый день ненавидел оба формата. Но альтернатива хуже: инлайн-маппинг в каждом хуке.
AI на Mistral
8 фич, все на Mistral API:
Диагностика по фото (Vision). Идентификация автостопщиков (тоже Vision). Анализ аквариума: смотрит на оборудование, параметры, обитателей, даёт оценку. SEO-описания для маркетплейса, за пару секунд. Модерация всего UGC на токсичность и спам, 11 типов контента. Суммаризация форума. Обогащение справочника: по названию рыбы заполняет все характеристики. Персональные рекомендации по улучшению системы.
Одна архитектурная деталь, которая оказалась полезной: пул API-ключей с round-robin. У Mistral щедрые бесплатные лимиты, но на один ключ их легко выжать. У меня 4 пула (chat, moderation, features, forum) с автоматическим fallback при 429. За два месяца я заплатил $0.
Зачем XP на платформе для рыбок
Аквариумистика, это хобби с длинным циклом. Запустил аквариум, месяц ждёшь азотный цикл, полгода растишь кораллы. Без причины заходить каждый день пользователь забывает о платформе через неделю.
XP-система решает это напрямую. Каждое полезное действие даёт опыт. Записал параметры воды, плюс 10 XP. Опубликовал статью, плюс 50. Ответил на форуме, плюс 5. Уровень считается по формуле floor(sqrt(xp/100)) + 1. Десять рангов. При повышении уровня вылезает тост с анимацией.
Вся логика в одной PostgreSQL-функции award_xp() с security definer. Клиент вызывает её fire-and-forget после успешного действия. Если RPC упал, ничего страшного, XP не критичен.
CREATE FUNCTION award_xp(p_user_id UUID, p_amount INT, p_reason TEXT) RETURNS JSONB AS $$ DECLARE v_new_xp INT; v_new_level INT; v_old_level INT; BEGIN SELECT xp, level INTO v_new_xp, v_old_level FROM users WHERE id = p_user_id; v_new_xp := COALESCE(v_new_xp, 0) + p_amount; v_new_level := GREATEST(1, FLOOR(SQRT(v_new_xp::FLOAT / 100.0))::INT + 1); UPDATE users SET xp = v_new_xp, level = v_new_level WHERE id = p_user_id; RETURN jsonb_build_object( 'xp', v_new_xp, 'level', v_new_level, 'leveled_up', v_new_level > v_old_level, 'xp_added', p_amount); END; $$ LANGUAGE plpgsql SECURITY DEFINER;
Честно, я не знаю, работает ли это на удержание. Данных пока мало. Но мне нравится, как лидерборд выглядит.
Безопасность: 12 атак, которые пришлось закрыть
Аукционная площадка с деньгами, это магнит для злоупотреблений. Я составил список из 12 векторов (назвал “Dirty Dozen”) и прошёлся по каждому.
Пользователь пытается изменить свою роль на admin через REST API. Подставляет чужой user_id в ставку. Сокращает время аукциона, чтобы выиграть. Ставит ставку ниже минимального шага. Пытается увидеть чужие заказы. Накручивает себе репутацию.
Всё закрывается через RLS и RPC. Ни один UPDATE на аукционные данные не идёт напрямую через REST. Только через хранимую процедуру, которая проверяет все инварианты. Это параноидально, но аукционы без этого не работают.
Архитектура
Internet → Nginx (SSL) → Docker Network ├── React + Express (frontend и API) ├── Supabase (13 сервисов) ├── Mosquitto MQTT ├── Prometheus + Grafana └── Portainer
93 таблицы. 122 RPC-функции. Два Docker Compose стека: Supabase и приложение.
Уведомления работают через PG LISTEN/NOTIFY, не через Supabase Realtime (об этом дальше). Триггер на INSERT в notifications кидает pg_notify, Express слушает канал через pg.Client и рассылает email через nodemailer и Telegram. Пользователь может настроить, какие типы уведомлений хочет получать в Telegram.
Приложение работает как PWA. Устанавливается на телефон, статика кэшируется. API и Supabase всегда идут через сеть, никогда через Service Worker.
Где я облажался и чему научился
Supabase Realtime на self-hosted это ловушка. Красивая идея: подписался на изменения таблицы, получаешь обновления. Реальность: на VDS с 4 GB RAM контейнер Realtime начинает падать и молча отключается. Я два дня дебажил, почему у пользователей не обновляются ставки на аукционе. Оказалось, Realtime просто умер и никому не сказал.
Убрал его полностью. Заменил на три паттерна: refetch после мутации, polling с setInterval для аукционов и чата (с обязательной паузой через usePageVisibility() в фоновых вкладках), и однократная загрузка для статики.
usePageVisibility() обязателен. Один забытый setInterval на 5 секунд в компоненте, который открыт у 50 пользователей в фоновых вкладках, это 600 запросов в минуту. Я нашёл это, когда Grafana показала подозрительную нагрузку в нерабочее время. Добавил паузу при скрытой вкладке, нагрузка упала вдвое.
RLS дебажить больно. Row Level Security в PostgreSQL отлично работает для безопасности. Но когда запрос возвращает пустой массив, ты не знаешь, данных нет или RLS отрезал. Приходится лезть в логи и перебирать политики одну за другой.
Один конкретный совет: всегда пишите (SELECT auth.uid()) вместо голого auth.uid() в политиках. Без скобок движок пересчитывает uid на каждую строку. С таблицей на 10 000 строк это заметно.
AI-модерация должна быть fail-open. Если AI-сервис упал, контент должен публиковаться, а не блокироваться. Модерация работает асинхронно: контент публикуется сразу, AI проверяет в фоне, при проблемах ставит флаг. Один раз Mistral API лежал 4 часа, и я был рад, что модерация не блокирует публикацию.
Один разработчик, это удобно до первого бага на проде в 3 ночи. Вся архитектура в голове, решения принимаются за секунды, нет созвонов. Но когда что-то падает, чинить тоже тебе. И тестировать. И деплоить. И отвечать на вопросы пользователей.
Цифры
Метрика | Значение |
|---|---|
Пользователей | 52 |
Видов в справочнике | 117 |
Болезней и автостопщиков | 56 |
Таблиц в БД | 93 |
RPC-функций | 122 |
AI-фич | 8 |
Время от первого коммита | ~8 недель |
Инфраструктура | ~$15/мес (VDS) |
AI | $0 (Mistral free tier) |
Проект молодой. Первый пользователь зарегистрировался 8 апреля 2026. Платформа уже работает: торговля идёт, аукционы проводятся, AI ставит диагнозы. Но 52 пользователя это не метрика успеха, это стартовая точка.
Что дальше
Мобильное приложение (PWA хорош, но нативные пуши работают лучше). Расширение IoT: больше контроллеров, автоматические алерты в Telegram. Совместные закупки живности. Может быть, англоязычная версия, если хватит сил.
Если у вас есть аквариум или вы думаете о нём, заходите на best-corals.ru. Если вы разработчик, у которого хобби превратилось в проект, расскажите в комментариях. Интересно, у кого что получилось.
React 19, Vite 6, Tailwind CSS 4, Motion, self-hosted Supabase (PostgreSQL 15), Express, Mistral AI, Mosquitto MQTT, Docker, Prometheus + Grafana. Один разработчик.
