На главную

Кейс · 02

PickMe — интернет-магазин и бот, который его наполняет

Срок 1 месяц разработки
Стек React 19 · Node · Postgres · Python · Gemini
Тип Full-stack + AI · E-commerce
Статус Доступен 24/7

Overview

Полноценный интернет-магазин в проде — и Telegram-бот, который сам наполняет каталог по фото из приватного канала владелицы. Собрано одним разработчиком за месяц.

Разработчик и владелица магазина один человек, и каждое продуктовое решение приходило из собственной практики. PickMe Store — магазин брендовой одежды с двумя визуальными темами под мужскую и женскую аудиторию и отдельным режимом страницы товара для подарка. PickMe Import Bot превращает пост в приватном канале в товар на сайте за 36 секунд.

Context

Портрет Валентины

Валентина — автор этого кейса и одновременно его клиент.

То, что заказчик и разработчик в этом проекте — один человек, в кейсе работает как сильная сторона. Все продуктовые решения — переключение тем, режим подарка, маркер незаполненных полей в админке, отдельное описание под Авито у каждого товара — приходили из практики работы с собственным каталогом и реальными покупателями.

До сайта продажи шли через Telegram-канал и Авито: разрозненные площадки с разными форматами карточек, без единого каталога и без возможности дать клиенту прямую ссылку на конкретный товар.

Problem

До сайта у магазина было три параллельных проблемы, каждая стоила денег.

Репутация

Продажи через канал в Telegram и витрину на Авито считываются клиентом как «у одного частного продавца», а не как магазин: нет домена, нет привычной адресной строки, нет «https»-замочка в браузере, нет страницы товара, на которую можно прислать ссылку другу для совета. Покупатель колеблется и не возвращается.

Каталог разорван по площадкам

Один и тот же товар нужно вручную вести в трёх местах одновременно: в Telegram-канале, на Авито и в личных складских табличках. Нет фильтров, нет поиска, нет страницы товара с замерами. Если клиент спросит «а есть размер L в чёрном?» — ответ ищется руками.

Ручная рутина наполнения

На каждый товар уходило 15–30 минут: открыть админку, залить четыре-пять фото, придумать описание, проставить категорию и пол, написать отдельный текст для Авито (там другой формат, другие требования к продающим словам, другая длина), вбить артикул и цену.

Constraints

На входе у проекта было четыре ограничения, и одно из них во многом определило всю топологию системы.

  1. 01
    Сама себе клиент → бюджет околонулевой. Нельзя купить Shopify, нанять команду, заказать брендинг. Бесплатные/дешёвые компоненты везде, где можно.
  2. 02
    Сайт-сервер должен быть в РФ. Российский домен .ru, индексация в Яндекс, репутация перед покупателями. А Gemini API на российских IP заблокирован — это автоматически отрезает прямой доступ к нему. Топология заранее оказалась двухзвенной: магазин в РФ, бот с обращениями к Gemini — на зарубежном VPS.
  3. 03
    Один разработчик и параллельная жизнь магазина. Продажи в Telegram + Авито шли всё время разработки. Сайт строился поверх живого бизнеса, без даунтайма для клиентов.
  4. 04
    Две аудитории в одном магазине. Мужская и женская одежда примерно в равных долях. Универсально-серая витрина «для всех» не считывается как «свой магазин» ни одной из сторон.

Solution

Самый общий принцип — собрать связку из двух минимально достаточных продуктов так, чтобы вместе они закрывали всю операционную рутину магазина.

PickMe Store — собственный e-commerce

Без Tilda и Shopify. Современный фронт-стек на React 19 с TypeScript и Vite, серверная часть на Node.js + Express, база на PostgreSQL и собственная админка с фильтрами, валидацией полей, массовым обновлением статусов. Своё API сделано как отдельный набор внутренних эндпоинтов под токен-секрет, чтобы извне в каталог никто не смог записать.

Админка PickMe — список товаров с фильтрами, поиском и сортировкой
Список товаров в админке с фильтрами, поиском и сортировкой
Админка PickMe — форма редактирования товара
Форма редактирования товара с автогенерированным описанием

Темы оформления через ThemeContext

Одни и те же компоненты, разные наборы цветовых и шрифтовых токенов. Переключение темы — одной кнопкой в шапке, без перезагрузки страницы. Поверх — отдельный режим страницы товара по адресу /gift/:id: то же фото, то же описание, но без цены и без кнопок действий, чтобы можно было переслать ссылку родственнику для согласования подарка.

Женская розовая тема PickMe Store
Женская тема — розовая, иронично-богатая
Мужская светлая тема PickMe Store
Мужская светлая — без декоративных элементов

PickMe Import Bot — AI-наполнение каталога

Telegram-бот на Python и aiogram, который превращает обычное поведение владелицы (постить фото вещи в свой приватный канал с короткой подписью) в публикацию товара на сайте.

Бот ловит пост, собирает фото из медиа-группы, скачивает их и парсит текст подписи: артикул, цена, размер, флаг «Авито: да». Дальше фото и текст уходят в Gemini 2.5 Flash через структурированный JSON-вывод — формат запроса, при котором модель обязана вернуть данные по заранее заданной схеме, а не свободный текст. На выходе получаем бренд, название, категорию, пол, описание для сайта и отдельное описание для Авито. Через внутреннее API сайта создаётся товар-черновик — он появляется в админке, и задача владелицы только проверить и нажать «опубликовать».

Окно «передумать» — 5 минут отложенной очереди

Между постом и обработкой проходит пять минут. Это сознательное окно: если пост опубликован по ошибке или в нём опечатка, его можно удалить из канала, и в каталог он не попадёт. Если за эти пять минут в канал прилетают добавочные фото с тем же media_group_id — они доклеиваются к уже стоящему в очереди посту.

Главный инженерный сюжет — обход сетевой блокировки

Прямой HTTPS-запрос с зарубежного сервера на сайт-сервер в РФ обрывался стабильно после ~9 КБ переданных данных: достаточно для короткого ответа, но мало для загрузки фото товара на сайт. Выяснилось, что это особенность фильтрации маршрута Нидерланды → РФ, не локальная конфигурация.

Решение — поднять SSH-туннель через autossh, завернуть его в systemd-юнит, который автоматически перезапускается при сбое, и сделать сам бот зависимым от этого юнита: пока туннель не поднят, бот не стартует. Для туннеля выпущен отдельный SSH-ключ с явным ограничением — он умеет только пробрасывать порт на конкретный сервис и больше ничего. Это не временный обход и не «костыль с туннелем», а штатный сервис на двух серверах с правильной изоляцией прав.

Главный инженерный сюжет

SSH-туннель — обход сетевой фильтрации NL → РФ

Tradeoffs

Часть решений — сознательный отказ от того, что выглядит привлекательно, но на текущем объёме не окупается.

  • Не Tilda и не Shopify, а свой стек. Tilda — потолок по кастомизации; Shopify — избыточен для нашего масштаба.
  • Не выкатывали векторную БД. Поиск по подстроке в Postgres + фильтры покрывают задачу. Когда каталог станет 1000+ — добавим.
  • SQLite в боте вместо Postgres. Боту нужно только отсеивать повторы постов и хранить историю — SQLite этого хватает.
  • Не делали мобильное приложение. Сайт адаптивен, на мобильных есть все три темы — нативное приложение не добавляет ценности на текущем объёме.

Results

Магазин работает в проде. На момент сборки кейса — 200+ товаров в каталоге, оформленных через бот, с фото, описаниями для сайта и отдельными описаниями для Авито.

  • ~36 секунд от поста владелицы в приватный канал до появления товара-черновика в админке: ловля поста, сборка медиа-группы, скачивание фото, парсинг текста, обращение к Gemini, создание записи через API.
  • Сайт в проде российский VPS в reg.ru (Москва), SSL от Let's Encrypt, индексация в Яндекс и Google, кэш статики в Nginx, ежесуточный бэкап БД в 3:00.
  • 3 темы оформления женская розовая, мужская светлая, мужская тёмная. Все шрифты, цвета, фоны и декор лежат в токенах — добавление новой темы делается без правки компонентов.
  • Режим подарка страница товара по адресу /gift/:id без цены и без кнопок, шеринг через системный диалог браузера (Web Share API).
  • Обход сетевой блокировки NL→РФ SSH-туннель с изолированным ed25519-ключом. Поднимается за 10 секунд после потери связи, восстанавливается без участия человека.
Пост в Telegram-канале
Пост владелицы в приватном канале
Карточка товара в админке после обработки ботом
Товар-черновик в админке через 36 секунд
Мобильная женская тема
Женская
Мобильная мужская тёмная тема
Мужская тёмная
Мобильная мужская светлая тема
Мужская светлая

Tech stack

Frontend — что отдаёт интерфейс покупателю

React 19 с TypeScript, сборка на Vite, Tailwind CSS 4 + shadcn/ui. Темы устроены через ThemeContext поверх CSS-переменных — три набора токенов (female / male-light / male-dark), переключение мгновенное. Web Share API для подарочной ссылки + fallback на копирование в буфер.

Backend — что обслуживает данные и интеграцию с ботом

Node.js на Express 5, логи через Pino. PostgreSQL + Drizzle ORM. Клиент API сгенерирован из OpenAPI через Orval, монорепо организовано через pnpm workspaces (9 пакетов). Внутренние эндпоинты для бота закрыты заголовком X-Bot-Token со сравнением через crypto.timingSafeEqual.

AI и бот — что наполняет каталог автоматически

Python 3.10+, aiogram 3.27+ в режиме long polling. Gemini 2.5 Flash с JSON Structured Output через Pydantic-схемы. Три отдельных системных промпта: на извлечение полей, на описание для сайта, на описание для Авито. httpx async, SQLite для дедупликации постов, tenacity для повторов на 5xx и сетевых ошибках.

DevOps и ops — на чём всё это живёт круглосуточно

Сайт — Ubuntu 22.04 на VPS в reg.ru (Москва), Nginx + PM2 + Certbot SSL, ежесуточный бэкап БД в 3:00. Бот — Ubuntu 22.04 на зарубежном VPS, systemd для запуска. Канал между серверами — autossh-туннель под управлением systemd-юнита pickme-tunnel.service, выделенный SSH-ключ с ограничением restrict, port-forwarding, permitopen="localhost:3000". SEO — sitemap.xml, robots.txt, Open Graph для каждого товара, регистрация в Яндекс.Вебмастере и Google Search Console.