Как я навайбкодил каталог бесплатных парковок Петербурга и России
Привет, сообщество! Уже долгое время я отмечал на для себя на карте бесплатные парковочные места в центре города; Этот список быстро начал «ходить по рукам» среди друзей и знакомых. Мне пришла в голову идея, а что если развить «списочек для друзей» в какой-то более общественно полезный проект? И с какими техническими челленджами придется столкнуться при попытке навайбкодить мало-мальски production-ready приложение?
Так появилась ГдеПарковка — карта бесплатных парковок, которую наполняют сами водители. Без регистрации: открыл, нашёл место рядом, поехал. Можно добавить точку, фото, комментарий, проголосовать «место живо / места нет», уточнить вместимость и построить маршрут в Яндекс Картах, 2ГИС или Google Maps.
Но ведь платные парковки существуют не просто так!
Легко понять негодование жителей центральных районов, которые не любят шум машин под окнами, а для своей машины, как зеницу ока, оберегают бесплатные «островки». Но треть пробок в центре — это машины, которые уже приехали. Они не едут из точки А в точку Б, они кружат по кварталу в поисках места. Классические исследования городского трафика (Дональд Шуп, UCLA) оценивают долю «поискового» трафика в загруженных районах в 20–30%. Водитель, который заранее знает, где встать, проезжает по вашему району один раз, а не наматывает пять кругов мимо окон, дворов и детских площадок. Карта парковок не добавляет машин в город. Она сокращает самый бессмысленный и раздражающий их пробег.
Да и даже жители центра зачастую стеснены в перемещениях платными парковками. Тот же житель Центрального района Санкт-Петербурга, даже имея прописку, не может просто так поехать по делам в Адмиралтейский район, даже если он начинается через квартал — льготные разрешения действуют только в рамках одного района.
Хаотичная парковка — от незнания, а не от злого умысла. Машина на газоне, на тротуаре, поперёк выезда «я на минутку» — почти всегда водитель, который не нашёл нормального места и сдался. Когда в трёхстах метрах есть легальный бесплатный карман и об этом можно узнать за десять секунд, у брошенной на клумбе машины исчезает оправдание.
Это социальный сервис, а не игрушка. Платная парковка в центре Петербурга стоит до 360 рублей в час, а в Москве еще в пару раз дороже. И пускай для офисного работника с парковкой от работодателя это мелочь, то для медсестры на смене, курьера, сантехника по вызову, человека, который возит родственника в больницу через день, это тысячи рублей в месяц из семейного бюджета. Информация о бесплатных местах всегда существовала — просто раньше она была привилегией местных и «своих». Цель сервиса — устранить эту несправедливость.
-
«Понаедут чужие в наш двор» — двор публикует только тот, кто хочет. Ваш двор без вас на карте не появится, а если появился и мешает, то жалуйтесь, уберём.
-
«Из-за вас больше машин» — количество машин определяется работой и делами, а не картой. Карта сокращает круги по кварталу, а не создаёт поездки.
-
«Пусть платят за парковку» — платят те, кто может. Остальные и так ищут бесплатные места, только медленно и объезжая по три раза ваш квартал.
-
«Вы легализуете парковку где попало» — наоборот: рейтинг и комментарии топят плохие места, а дисклеймер прямо требует проверять знаки. У хаоса появился фильтр.
Техническая часть
Я не написал руками ни строчки кода. Весь проект — от схемы БД до деплоя — сделан в диалоге с Claude Code, за пару вечеров, с помощью модели Fable.
Босяцкий хак проекта: импорт из закладок Яндекс Карт
У меня была своя подборка из 68 бесплатных парковок Петербурга в закладках Яндекс Карт. Экспорта у Яндекса нет. Зато можно поделиться своей подборкой по ссылке, и публичная подборка это SSR, и в HTML лежит <script class=»state-view»> с полным JSON-состоянием, включая все точки:
const state = JSON.parse(html.match(/<script[^>]*class=»state-view»[^>]*>(.*?)</script>/s)[1]); const items = state.config.bookmarksPublicList.children; // { title: «Парковка», uri: «ymapsbm1://pin?ll=30.290309%2C59.970660», // description: «Санкт-Петербург, улица Профессора Попова, 40» }
Координаты достаются прямо из ymapsbm1://pin?ll=…. Скрипт импорта идемпотентен — точки ближе 30 метров к существующим пропускаются, так что его можно гонять повторно. Так у сервиса в день запуска появился стартовый каталог по Петербургу, а не пустая карта.
Стек
Я придерживаюсь мнения, что при кодинге с ИИ-шкой лучше использовать хорошо задокументированные и скучные технологии. Грубо говоря, используя модный и не сильно хорошо описанный фреймворк, либо сильно меняющийся между версиями, то он будет слабее отражен в весах модели, и будут возникать ошибки. Помню, как я писал экспорт скрипты для KIWI TCMS, и клод вообще не мог сконструировать валидный запрос, пока я не копипастнул ему в контекст избранные файлы из имплементации самого KIWI TCMS, только тогда он разобрался в формате апишки. Поэтому в промпте был заложен принцип: никаких фреймворков. Такое тривиальное архитектурное решение сработало вполне хорошо!
-
Бэкенд: Node.js + Express + SQLite. Причём не better-sqlite3, а встроенный node:sqlite из Node 22+ — ноль нативных зависимостей, npm install не может сломаться в принципе.
-
Фронтенд: vanilla JS + Leaflet с тайлами OSM. Без сборки вообще — файлы из public/ отдаются как есть. Никакого вебпака, вайта, транспиляции. Деплой фронта — это git pull.
-
Зависимостей в package.json — четыре: express, multer, sharp, compression. И leaflet, который отдаётся статикой из node_modules.
В сумме: один процесс, одна БД-файл, один каталог data/ со всем состоянием. Бэкап — это sqlite3.backup плюс rsync каталога фотографий, восстановление — скопировать каталог обратно.
Хостинг — VPS на RuVDS за ~300 ₽/мес: 1 vCPU, 1 ГБ RAM. Перед Node стоит Caddy, HTTPS с автопродлением сертификата из коробки. Кстати, HTTPS тут не опция: браузеры отдают navigator.geolocation только в secure context.
Простота использования и секьюрность
Секьюрности нет)))
Защита от накрутки простенькая. «Без регистрации» и «один голос на человека» — требования, противоречащие друг другу. Разрешение конфликта — анонимный clientId (UUID в localStorage) плюс второй рубеж: в таблице голосов хранится солёный хэш IP, и с одного адреса на одну точку принимается не больше пяти голосов от разных «клиентов». Запас — на CGNAT и общий Wi-Fi, сырые IP не хранятся вообще. Это не защита от целеустремлённого ботовода, это защита от накрутки «за пять минут скриптом». Для моего кейса это осознанно достаточный уровень.
Фотографии: сжимаем варварски, и это фича
Пользовательские фото парковок — это «общий план двора». Качество не нужно от слова совсем, поэтому сжатие агрессивное: JPEG quality 45 (mozjpeg), ресайз до 1024px по большей стороне. Телефонное фото на 6 МБ превращается в 50–150 КБ.
await sharp(buffer, { limitInputPixels: 30_000_000 }) .rotate() // применить EXIF-ориентацию до удаления метаданных .resize({ width: 1024, height: 1024, fit: ‘inside’, withoutEnlargement: true }) .jpeg({ quality: 45, mozjpeg: true }) .toFile(path);
В этих пяти строчках три неочевидных решения:
-
limitInputPixels — защита от pixel-flood. JPEG 50000×50000 весит меньше 15 МБ, но при декодировании разворачивается в гигабайты RAM. На VPS с гигабайтом памяти это гарантированный OOM. Лимит в 30 мегапикселей режет бомбу на этапе чтения заголовка.
-
Пересжатие как санитайзер. Что бы ни прислали под видом картинки — полиглот-файл, картинку со скриптом в метаданных — на диск ложится честный JPEG, собранный заново.
-
Re-encode убивает EXIF — включая GPS-координаты автора снимка. Для краудсорсингового сервиса это не мелочь: человек фотографирует парковку у дома.
Плюс все фото проходят через последовательную очередь (sharp.concurrency(1)) — два параллельных декода больших снимков на маленьком VPS складывают процесс, проверено намеренно.
А поскольку имена файлов — UUID и не меняются никогда, отдаются они с Cache-Control: immutable, max-age=1y: повторные открытия карточек не трогают сервер вообще.
Автомодерация комментариев: 70 строк вместо npm-пакета
Хотелось автоматом резать мат и спам-дубликаты до того, как они падают в очередь модератору. Готовые пакеты для русского мата на npm оказались заброшены и наивны, ML-API — платная зависимость с латентностью на каждый комментарий. В итоге — собственный модуль на 70 строк: словарь корней + нормализация против обфускации.
Нормализация делает из «м у д @ к» и «сууукааа» канонические формы: латиница-двойники и цифры приводятся к кириллице, символы-маски выбрасываются, повторы букв схлопываются, «р а з р я д к а» склеивается обратно в слово. А анкеры в регулярках подобраны так, чтобы «курс рубля», «колебание», «обед», «веб-камера» и «не психуй» проходили спокойно. На 24 тестовых кейсах — ноль промахов в обе стороны.
Дубликаты ловятся сравнением нормализованного текста: повтор у той же точки или три одинаковых текста за сутки по всем точкам — до свидания. Отклонённое автоматикой в БД не пишется вообще: спамер не растит нам диск.
Интерфейс

Интерфейс mobile-first и бессовестно вдохновлён ГдеБЕНЗом: сверху компактная плашка-дисклеймер, дальше карта на весь экран с зелёными, красными и серыми пинами — цвет считается из баланса голосов «место живо / места нет». На телефоне между картой и списком переключаешься пилюлей сверху, на десктопе список висит слева постоянно.
Тап по пину открывает нижнюю шторку-карточку, и в ней весь функционал: описание, вместимость по категориям (от «маленькая, 1–4 места» до «огромная, 50+»), голосование с возможностью передумать, фото, комментарии и история голосов. Там же кнопка «Построить маршрут», которая пробрасывает точные координаты точки в Яндекс Карты, 2ГИС или Google Maps: на телефоне ссылку перехватывает установленное приложение и сразу строит маршрут. Добавление парковки — три действия: кнопка, тап по карте, короткая форма с опциональным фото. Регистрации нет нигде, «личность» пользователя — UUID в localStorage. Ну и PWA: сайт ставится на домашний экран и открывается как приложение, без адресной строки. Также есть админка со статистикой: посещения по дням, счётчики действий, топ точек по рейтингу и «самые спорные» — места, у которых много и 👍, и 👎, то есть парковки-лотереи.
Выводы
Проект от «а сделаю-ка я» до продакшена с доменом, HTTPS, PWA, SEO и стартовым каталогом занял, суммарно, пару вечеров диалога. Что показывает, что говнопалочные пет-проекты сейчас стало делать проще, чем никогда — в целом, с новыми моделями уже не особо важны технические знания, даже первая итерация по расплывчатому ТЗ более-менее работала и даже неплохо выглядела. Но имея инженерные знания, можно промптить модель так чтобы она закрывала бреши в производительности и безопасности, делать ей всякие замечания и тп. Ну и главное, работает стабильно, лучше чем мои курсовые с первого курса 🙂
Что еще помогло не накосячить с ИИшкой: скучный стек, коммиты на каждый шаг и живое тестирование руками на реальном устройстве.
Сервис живой: gdeparkovka.ru. По Петербургу каталог уже есть, по другим городам карта ждёт ваших точек, добавление занимает полминуты и не требует регистрации. Критику проекта жду в комментариях! Ну и пользуйтесь на здоровье, информация это общественное благо 🙂
Источник: habr.com
Похожие записи
Оцените материал:
Похожие записи
Искусственный интеллект в разработке видеоигр: как он меняет индустрию.
01.06.2026
Корпорация добра продолжает свой крестовый поход по внедрению ИИ туда, где его никто не просил
13.01.2026
Дешевое локальное агентное программирование: Claude Code + Ollama + Gemma4
10.06.2026Присоединяйтесь и подпишитесь на рассылку самых свежих новостей по Email
Получайте свежие новости и идеи на почту. Без спама — только самое интересное.
Нажимая «Подписаться», вы соглашаетесь с политикой конфиденциальности.
