Client Personal Demo (Open Source)
Role Solo Creator / Architect
Period Aug 2026
Stack
Go chi pgx/v5 PostgreSQL JWT Docker Compose
fleet-crm — бекенд CRM для автопарку/логістики preview

fleet-crm — бекенд CRM для автопарку/логістики

Огляд

Незалежний demo-проєкт: REST API на Go для диспетчерської CRM автопарку — клієнти, техніка, водії, життєвий цикл нарядів, виставлення рахунків та telematics-webhook для прийому GPS/показань одометра від зовнішнього обладнання в реальному часі.

Побудовано, щоб продемонструвати патерн, який регулярно повторюється у фріланс-запитах — диспетчерській компанії потрібна CRM, що зв’язує клієнтів, техніку та білінг, живиться від зовнішнього telematics/GPS-провайдера — з тією ж інженерною дисципліною, що й у клієнтських проєктах: репозиторії на інтерфейсах, стейт-машина замість довільної колонки статусу, і кожне твердження нижче перевірене на живому бінарнику, а не лише юніт-тестами.

Результат: повний CRUD + auth + перевірена стейт-машина нарядів + webhook, з одним реальним cross-entity багом, знайденим і виправленим ще до першого коміту.


Архітектура

Доменні зв'язки fleet-crm

WorkOrder пов’язує Client + Vehicle (+ опційно Driver) і рухає два процеси: Invoice, лише після завершення, та статус самого vehicle — через окремий частковий update, а не заміну всього рядка (чому це важливо — нижче). Telematics-webhook — єдиний неавторизований шлях запису в API — захищений спільним секретом, не JWT, оскільки це machine-to-machine взаємодія.


Ключові інженерні рішення

Рішення Чому
Переходи наряду перевіряються проти єдиної таблиці легальних ребер scheduled → in_progress/cancelled, in_progress → completed/cancelled, обидва термінальні стани закриті. Наївна колонка статусу дозволила б перескочити одразу в completed або “роззавершити” вже виконаний наряд — нелегальні переходи повертають 409 з типізованою ErrIllegalTransition.
Статус vehicle змінюється через UpdateStatus (частковий), ніколи через Update (весь рядок) Кожен Repository — інтерфейс, від якого залежить код Service/Handler — замінний на in-memory fake у тестах, БД для go test ./... не потрібна.
Створення рахунку спершу перевіряє, що наряд у статусі StatusCompleted Виставлення рахунку до фактичного завершення роботи — реальний клас багів, не гіпотетичний — це перевіряється на рівні сервісу, а не лишається на совість клієнта API.
Telematics-webhook використовує crypto/subtle.ConstantTimeCompare, не == Звичайне порівняння рядків для секрету в заголовку витікає timing-інформацією, пропорційною довжині співпадаючого префікса — дрібниця, але саме та, що відрізняє “прочитав туторіал” від “реально подумав про це”.
Жодного migration-фреймворку для 6 таблиць Власноруч написаний runner на embedded SQL (internal/db) — таблиця applied_migrations плюс відсортоване виконання через embed.FS. Простіше, ніж тягнути залежність заради схеми такого розміру.

Баг, який мав значення: рання версія побічного ефекту “наряд → техніка” викликала загальний vehicle.Update(id, vehicle.Vehicle{Status: newStatus}) — передаючи структуру, де заповнено лише Status. Оскільки Update заміняє весь рядок, кожна зміна статусу наряду мовчки занулила б номер, тип та показання одометра техніки. Спіймано регресійним тестом, що перевіряє виживання цих полів при переході, і підтверджено вдруге живим прогоном docker compose перед першим комітом — не просто довірено тому, що тест пройшов.


Результати

  • Повний REST API: JWT-авторизація (bcrypt + 8-годинні токени, без публічної реєстрації), CRUD для клієнтів/техніки/водіїв, життєвий цикл нарядів, виставлення рахунків, telematics-webhook
  • Один реальний cross-entity баг знайдено і виправлено до коміту — баг втрати даних Update-проти-UpdateStatus вище
  • Репозиторії на інтерфейсах всюди — кожен тест сервісу/хендлера працює проти in-memory fake; go test ./... -race проходить чисто, БД не потрібна
  • Перевірено наскрізно, не лише юніт-тестами: docker compose up → міграції застосовуються при старті → login засіяного admin → повний CRUD → нелегальний перехід відхилено (409) → легальний перехід підтверджено, включно з тим, що недоторкані поля техніки виживають → рахунок-до-завершення відхилено (409) → рахунок успішний після завершення → обидва шляхи webhook (невірний секрет відхилено, вірний секрет оновлює техніку) підтверджено на живому бінарнику

Код: github.com/valpere/fleet-crm


Стек

Go · chi (router) · pgx/v5 (без ORM) · golang-jwt · bcrypt · PostgreSQL · Docker Compose · testify