Универсальная система управления проектами в портфолио для bozheslav.ru. Поддерживает три разных типа размещения работ — от интерактивных демо до ссылок на внешние ресурсы — с единой админкой в Filament.
— Порядок маршрутов критичен: конкретные /portfolio/demo/{slug}, /portfolio/pages/{slug} должны быть выше универсального /{slug} catch-all в routes/web.php — Laravel берёт первое совпадение
— Nginx vs Laravel роутинг: location-блок без try_files $uri $uri/ /index.php?$query_string тихо перехватывал динамические пути — классическая ошибка на production
— Slug-based storage: имена папок distro-safe (только латиница, дефисы) — избегает проблем с кириллицей в файловой системе
— Три типа проектов:
• Demo — интерактивные работы с живым кодом (HTML/CSS/JS). Загружается ZIP-архивом, автоматически распаковывается в storage/demo/{slug}/, становится доступен по URL вида /portfolio/demo/{slug}. Автор загружает архив — не думает про FTP и распаковку
• Page — полноценные страницы-кейсы со своей вёрсткой, хранятся в таблице portfolio_pages. Для детальных разборов проектов с иллюстрациями и текстом
• External — ссылки на внешние работы (live-сайты, GitHub, статьи на сторонних ресурсах). Карточка ведёт на внешний URL
— Единая админка в Filament:
• Один Resource PortfolioResource управляет всеми типами через dropdown type
• Поля формы динамически меняются в зависимости от выбранного типа
• Для Demo — поле загрузки ZIP с preview файлов после распаковки
• Для Page — связь с portfolio_pages таблицей
• Для External — просто поле URL
— Auto-unpack ZIP для Demo:
• afterCreate() хук в Filament извлекает архив
• Валидация структуры (должен быть index.html в корне)
• Автоматическая очистка старых файлов при перезагрузке архива
• Защита от path traversal при распаковке
— Маршрутизация:
• /portfolio/demo/{slug} → статика из storage/demo/
• /portfolio/pages/{slug} → PortfolioPageController + Blade-шаблон
• /portfolio → каталог всех работ с фильтрацией по типу/категории
• Nginx location ^~ /portfolio/ с корректным try_files — статика не перехватывает динамические роуты