Привет, Я Ваня Демидов, разработчик Selectel. В этой статье я расскажу о своем опыте использования фреймворка Nuxt3 и Pocketbase — open source-решения для хранения данных.
Задумка и исходники
Осенью наша компания решила запустить платформу, на которой сотрудники могли бы купить ненужную офисную мебель. Например, ту, что перестала подходить к обновленному дизайну переговорок, кофе-пойнтов или рабочего спейса. Так кресла и столы получили бы вторую жизнь, Selectel избавился от излишков, а сотрудники приобрели хорошую мебель по низкой стоимости.

Для начала предстояло оценить фронт работ: собрать данные и продумать механику платформы. Коллеги из других отделов помогли сфотографировать мебель, определить степень ее износа и стоимость. Все данные мы занесли в Excel-таблицу.
После перешли к самой механике. Нам было важно, чтобы распродажа проходила честно. Если бы мы просто отправили в рабочий чат ссылку на список мебели, то покупателем стал тот, кто первым отреагировал на сообщение. Мы же хотели дать возможность поучаствовать всем коллегам.
Тогда нам пришла идея розыгрыша: создать сайт с каталогом мебели и показывать, сколько дней осталось до ее продажи. До этого времени любой коллега может посмотреть ассортимент и решить, хочет ли он что-то приобрести. Далее, если ему интересна тумба, кресло или что-то еще, он оставляет заявку. Наконец, в назначенный день мы собираем все заявки и определяем среди них победителя рандомным образом.
Почему PocketBase и Nuxt3
Мне стало интересно реализовать такую задумку, поэтому я взялся за разработку сайта.
Если оценить в целом, то платформа напоминает некий интернет-магазин с долей рандома. Но перед тем как приступить к разработке, необходимо понять, какая функциональность требуется и какая технология подойдет лучше.
Какие были требования?
- Информация о товарах должна где-то лежать — то есть нужна база данных. Также желательно, чтобы у нее была панель администратора для визуализации и простоты восприятия. На тот момент в Selectel работало более 1 000 сотрудников, поэтому большой нагрузки на базу не предвиделось.
- Чтобы определить личность участника, необходима аутентификация. Важное условие: участвовать может только сотрудник компании, поэтому нужно запретить регистрацию пользователей из внешнего мира.
- Для регистрации аккаунта требуется подтверждение по почте — понадобится почтовый сервер.
- Для отображения интерфейса нужен фреймворк. Желательно что-то простое и знакомое, так как для разработки задействован только один человек — я.
Изначально я хотел для фронта использовать React, а для бэкенда — WordPress, но передумал. У меня нет большой экспертизы в этой библиотеке, да и многочисленные настройки CMS оказались ни к чему. Так что мой выбор пал на Nuxt3 и PocketBase.
С Nuxt3 все просто: он хорошо мне знаком, ведь мы пишем на нем наш сайт. Кроме того, у него есть режим статической генерации страниц — на мой взгляд, это самый подходящий для проекта подход к конструированию страниц. А еще Nuxt3 из коробки предоставляет клиентский роутинг без перезагрузки страницы — современное и эстетичное решение. Ну и вишенка, у него хорошая и подробная документация.
А вот PocketBase мне подсказал коллега, когда я рассказал ему про требования к бэкенду. В итоге инструмент хорошо подошел под мои задачи: привлекли простота и наличие всех необходимых фишек.
Оказалось, что встроенная аутентификация здесь настолько же проста, насколько разнообразна. Здесь можно настроить аутентификацию по паролю, OTP, OAuth2 или выбрать другие варианты. Я остановился на первом: он вполне безопасный и простой.
Так как PB предоставляет готовый способ аутентификации, здесь есть и готовый почтовый сервер, который настраивается за несколько минут. А еще панель администратора выглядит стильно и минималистично — как раз подходит под мои требования:

Еще в PocketBase легко создавать реляционные таблицы. При создании появляется интуитивно понятный интерфейс управления таблицей, удобный интерфейс управлением API и готовый Swagger:

Осталось лишь понять, хватит ли ресурса этой БД (а она использует SQLite), чтобы обеспечить непрерывную работу при 1 000 активных пользователях единовременно. Оказалось, что да — PocketBase выдерживает до 10 000 активных пользователей. Нагрузка также зависит от количества операций, но и их будет не так много.
Какие были трудности
Пока я разрабатывал проект, вышла новая версия PocketBase. На тот момент мне показалось хорошей идеей использовать ее, как наиболее актуальную. Но когда я начал импортировал уже настроенные коллекции с прошлой версии, получил ошибку:

Пожалуй, это единственный нюанс, с которым я столкнулся. Пришлось заново создать необходимые таблицы, благо это не заняло много времени.
На самом деле, у PB есть и другие недостатки. Главный — эту технологию все еще нельзя использовать в серьезных проектах. О чем, кстати, написано на первой же странице документации. Но этот «временный» минус можно не брать в расчет, если вам нужно быстрое и современное решение для пет-проекта вроде моего.
Когда все таблицы были вновь заведены, можно было приступать к наполнению их данными. Я писал выше, что вся информация о мебели лежит в Excel — это примерно 100 позиций. Переносить вручную такое количество данных — пустая трата времени, гораздо удобнее (и быстрее) было автоматизировать этот процесс.
Какие таблицы в БД понадобились:
- пользователи — очевидно;
- продукты — не менее очевидно;
- запросы — вместо того, чтобы создавать отдельное поле в каждом товаре, например usersId, мне захотелось сделать отдельную таблицу, где были userId + productId.

Легкий скрипт на Node.js, запущенный локально, перенес заранее сконвертированный JSon со всей «мебельной» информаций в базу — и здесь меня вновь подстерегала незадача. В таблице оказались ссылки на фото мебели, доступ к которым был ограничен.
На тот момент я не придумал ничего лучше, чем занести вручную все фотографии. Но сейчас, когда пишу эту статью, меня не покидает чувство стыда: надо было просто скачать эти фото и передать рутинную работу коду.
Еще одна проблема появилась в конце разработки, когда я показал приложение коллегам из отдела безопасности. В статье «От идеи до эксплойта» можно почитать об уязвимостях, которые они нашли. Но к слову, здесь мне вообще не стыдно, потому что я уже все пофиксил.
Логика работы сайта
Сам сайт находится за балансировщиками, поэтому единственная возможность увидеть его — зайти из нашей офисной сети. У него есть несколько страниц:
- общая — со всеми товарами;
- правила участия в розыгрыше;
- корзина добавленные в корзину товары больше не показываются в общей ленте);
- страница входа/регистрации;
- и самая, на мой взгляд, интересная — карточка продукта. Про нее и поговорим.

На странице продукта находятся фото мебели, ее характеристики и кнопки для участия — в скобках указывается количество участников. Эти кнопки показываются только авторизованным пользователям. Также рядом с ними я разместил часть, которую видят только администраторы — кнопку розыгрыша. Задумка была такая: когда настанет время, пользователь-администратор (важно, что это не разработчик, а любой человек с правами администратора) заходит на страницу товара и выбирает победителя.

Если товар добавлен в корзину по ошибке или участник передумал, то его можно удалить.
Сам сайт был статическим, но «товары» на нем динамические. Именно поэтому мне стало интересно реализовать возможность динамического добавления предметов в админку и последующего отображения без необходимости пересборки. На помощь пришли query-параметры, которые я считывал на странице /products/product?id=${productId} при ее монтировании. Где id — это productId, по которому я получал информацию о товаре и запросах других пользователей.
Страница со всеми продуктами выглядит так:

Из особенно удобного на ней — фильтрация по категориям. Изначально в документе нет данных о категории, но я добавил очередной скрипт на Node.js — пробежался по массиву данных и в зависимости от слов, встречающихся в названии товара — поставил категорию.
Добавляем еще немного заботы о коллегах в виде хлебных крошек, ссылок на терминологию и другие страницы приложения. Собираем красивую иконку из тирекса и маленькой тележки — и можно в прод.
Итоги
Магазин был успешно запущен и не лег в момент запуска распродажи. Я был счастлив, что никто из коллег не пришел спрашивать, как работает интерфейс. Это значит, с проектированием я тоже справился. И даже регистрация с подтверждением по почте не вызвала негодования, хотя часто мне задавали вопрос, почему не использовал SSO.
Да, не обошлось без нюансов. Так как у коллег была возможность отказаться от выигранных позиций, часто (раз 10–20) приходилось вручную в базе сбрасывать победителя и разыгрывать товары снова. Считаю, что функция getAnotherWinner была бы очень полезна. Но в остальном сайт работает как нужно — а значит, проект удался.
