# Техническое задание: Каталог рецептов с рейтингом ## 1. Описание проекта Кулинарная платформа для публикации рецептов, комментирования и оценки блюд. Проект закрепляет навыки CRUD, отношений многие-ко-многим (рецепт ↔ ингредиенты), расчёта среднего рейтинга, разграничения прав и совместной работы через Git. **Команда:** 2–3 человека --- ## 2. Функциональные требования ### 2.1. Аутентификация и регистрация - Регистрация, вход, выход (Laravel Breeze) ### 2.2. Каталог рецептов - Просмотр списка рецептов с пагинацией - Фильтрация по категориям (завтрак, обед, ужин, выпечка, десерты, напитки) - Поиск по названию и ингредиентам - Сортировка по дате добавления, рейтингу, времени приготовления - Детальная страница рецепта ### 2.3. Управление рецептами (автор/админ) - CRUD рецептов: название, описание, категория, время приготовления, количество порций, изображение, сложность - Пошаговое приготовление (несколько шагов, каждое — отдельная запись) - Привязка ингредиентов с количеством и единицей измерения - Загрузка изображения рецепта (валидация: jpg/png, макс. 2MB) ### 2.4. Ингредиенты - Справочник ингредиентов (название, единица измерения по умолчанию) - CRUD ингредиентов (админ) - Привязка к рецептам (многие-ко-многим) с указанием количества и единицы ### 2.5. Комментарии - Добавление комментариев к рецепту - Удаление своего комментария - Удаление любого комментария (админ/автор рецепта) - Пагинация комментариев ### 2.6. Оценки и рейтинг - Оценка рецепта (1–5 звёзд) — один голос на пользователя - Отображение среднего рейтинга на карточке и странице рецепта - Автоматический пересчёт среднего рейтинга ### 2.7. Избранное - Добавление рецепта в избранное - Список избранных рецептов (личный кабинет) ### 2.8. Личный кабинет автора - Мои рецепты - Статистика: количество рецептов, средний рейтинг, количество комментариев - Избранные рецепты ### 2.9. Панель администратора - Управление пользователями и ролями - CRUD ингредиентов (справочник) - Модерация рецептов и комментариев - Общая статистика --- ## 3. Структура базы данных ### Таблица `users` | Поле | Тип | Описание | |---|---|---| | id | BIGINT UNSIGNED | Первичный ключ | | name | VARCHAR(255) | Имя | | email | VARCHAR(255) | Email (unique) | | password | VARCHAR(255) | Хеш пароля | | role | ENUM('admin', 'author', 'user') | Роль | | avatar | VARCHAR(255) \| NULL | Аватар | | created_at | TIMESTAMP | | | updated_at | TIMESTAMP | | ### Таблица `categories` | Поле | Тип | Описание | |---|---|---| | id | BIGINT UNSIGNED | Первичный ключ | | name | VARCHAR(255) | Название категории | | slug | VARCHAR(255) | URL-идентификатор (unique) | | created_at | TIMESTAMP | | | updated_at | TIMESTAMP | | ### Таблица `recipes` | Поле | Тип | Описание | |---|---|---| | id | BIGINT UNSIGNED | Первичный ключ | | title | VARCHAR(255) | Название рецепта | | slug | VARCHAR(255) | URL-идентификатор (unique) | | description | TEXT | Краткое описание | | image | VARCHAR(255) \| NULL | Изображение | | category_id | BIGINT UNSIGNED | Категория (FK → categories) | | author_id | BIGINT UNSIGNED | Автор (FK → users) | | cooking_time | INT | Время приготовления (минуты) | | servings | INT | Количество порций | | difficulty | ENUM('easy', 'medium', 'hard') | Сложность | | rating | DECIMAL(3, 2) \| NULL | Средний рейтинг (0.00–5.00) | | rating_count | INT | Количество оценок | | is_published | BOOLEAN | Опубликован ли | | created_at | TIMESTAMP | | | updated_at | TIMESTAMP | | ### Таблица `recipe_steps` | Поле | Тип | Описание | |---|---|---| | id | BIGINT UNSIGNED | Первичный ключ | | recipe_id | BIGINT UNSIGNED | Рецепт (FK → recipes) | | step_number | INT | Порядковый номер шага | | description | TEXT | Описание шага | | created_at | TIMESTAMP | | | updated_at | TIMESTAMP | | ### Таблица `ingredients` | Поле | Тип | Описание | |---|---|---| | id | BIGINT UNSIGNED | Первичный ключ | | name | VARCHAR(255) | Название ингредиента (unique) | | default_unit | VARCHAR(50) | Единица измерения по умолчанию (г, мл, шт, ст.л.) | | created_at | TIMESTAMP | | | updated_at | TIMESTAMP | | ### Таблица `ingredient_recipe` (многие-ко-многим) | Поле | Тип | Описание | |---|---|---| | id | BIGINT UNSIGNED | Первичный ключ | | recipe_id | BIGINT UNSIGNED | FK → recipes | | ingredient_id | BIGINT UNSIGNED | FK → ingredients | | quantity | DECIMAL(10, 2) | Количество | | unit | VARCHAR(50) | Единица измерения | ### Таблица `comments` | Поле | Тип | Описание | |---|---|---| | id | BIGINT UNSIGNED | Первичный ключ | | body | TEXT | Текст комментария | | user_id | BIGINT UNSIGNED | Автор (FK → users) | | recipe_id | BIGINT UNSIGNED | Рецепт (FK → recipes) | | created_at | TIMESTAMP | | | updated_at | TIMESTAMP | | ### Таблица `ratings` | Поле | Тип | Описание | |---|---|---| | id | BIGINT UNSIGNED | Первичный ключ | | user_id | BIGINT UNSIGNED | FK → users | | recipe_id | BIGINT UNSIGNED | FK → recipes | | rating | TINYINT | Оценка (1–5) | | created_at | TIMESTAMP | | | updated_at | TIMESTAMP | ### Таблица `recipe_user` (избранное) | Поле | Тип | Описание | |---|---|---| | id | BIGINT UNSIGNED | Первичный ключ | | user_id | BIGINT UNSIGNED | FK → users | | recipe_id | BIGINT UNSIGNED | FK → recipes | | added_at | TIMESTAMP | | --- ## 4. Маршруты и контроллеры ```php // Публичные маршруты Route::get('/', [RecipeController::class, 'index'])->name('recipes.index'); Route::get('/recipes/{recipe:slug}', [RecipeController::class, 'show'])->name('recipes.show'); // Аутентифицированные маршруты Route::middleware('auth')->group(function () { // Оценки Route::post('/recipes/{recipe}/rate', [RatingController::class, 'store'])->name('recipes.rate'); // Избранное Route::get('/favorites', [FavoriteController::class, 'index'])->name('favorites.index'); Route::post('/recipes/{recipe}/favorite', [FavoriteController::class, 'toggle'])->name('recipes.favorite'); // Комментарии Route::resource('recipes.comments', CommentController::class)->scoped(['recipes' => 'recipe'])->only(['store', 'destroy']); // Мои рецепты (автор) Route::prefix('my-recipes')->name('my-recipes.')->middleware('can:be-author')->group(function () { Route::resource('recipes', AuthorRecipeController::class); Route::get('dashboard', [AuthorDashboardController::class, 'index'])->name('dashboard'); }); // Админка Route::prefix('admin')->name('admin.')->middleware('can:access-admin-panel')->group(function () { Route::resource('users', AdminUserController::class); Route::resource('ingredients', AdminIngredientController::class); Route::patch('recipes/{recipe}/toggle-publish', [AdminRecipeController::class, 'togglePublish'])->name('recipes.publish'); Route::get('dashboard', [AdminDashboardController::class, 'index'])->name('dashboard'); }); }); ``` ### Контроллеры - `RecipeController` — публичный каталог и детальный просмотр - `AuthorRecipeController` — CRUD рецептов автором - `RatingController` — добавление/обновление оценки - `FavoriteController` — управление избранным - `CommentController` — создание/удаление комментариев - `AuthorDashboardController` — панель автора - `AdminUserController`, `AdminIngredientController`, `AdminRecipeController`, `AdminDashboardController` — админка --- ## 5. Роли, Gates и Policies ### Роли | Роль | Описание | |---|---| | `admin` | Полный доступ, модерация, CRUD ингредиентов, управление пользователями | | `author` | Создание/редактирование своих рецептов, управление комментариями к своим рецептам | | `user` | Просмотр каталога, комментирование, оценка, добавление в избранное | ### Gates ```php Gate::define('be-author', function (User $user) { return in_array($user->role, ['admin', 'author']); }); Gate::define('access-admin-panel', function (User $user) { return $user->role === 'admin'; }); ``` ### Policies **RecipePolicy:** - `view` — все (если опубликован), автор/admin (если не опубликован) - `create` — author или admin - `update`, `delete` — автор рецепта или admin - `rate` — user (ещё не оценивал этот рецепт) **CommentPolicy:** - `create` — все аутентифицированные - `delete` — автор комментария, автор рецепта или admin **IngredientPolicy:** - `view` — все - `create`, `update`, `delete` — только admin --- ## 6. Требования к интерфейсу (Bootstrap 5) ### Компоненты - **Навбар** — логотип, каталог, «Избранное», «Мои рецепты», профиль - **Карточки рецептов** — изображение, название, категория (бейдж), время (иконка + текст), рейтинг (звёзды) - **Страница рецепта** — большое изображение, описание, ингредиенты (таблица), шаги приготовления (нумерованный список), комментарии, форма оценки - **Звёздный рейтинг** — интерактивные звёзды (radio buttons + стилизация) - **Форма рецепта** — Bootstrap-форма с загрузкой изображения, динамическое добавление шагов и ингредиентов - **Таблица ингредиентов** — название, количество, единица измерения - **Дашборд автора** — статистика (карточки), список моих рецептов - **Адаптивная сетка** — `col-md-4` для карточек, `col-md-8` + `col-md-4` для основного контента и сайдбара ### Цветовая схема сложности - Лёгкий — `bg-success` - Средний — `bg-warning` - Сложный — `bg-danger` ### Цветовая схема категорий - Завтрак — `bg-info` - Обед — `bg-primary` - Ужин — `bg-dark` - Выпечка — `bg-warning` - Десерты — `bg-pink` - Напитки — `bg-light text-dark` --- ## 7. Git-workflow для команды ### Распределение модулей (для 2 человек) | Участник | Модуль | Ветки | |---|---|---| | **Участник A** | Рецепты, категории, шаги, ингредиенты, каталог | `feature/recipes-crud`, `feature/categories`, `feature-recipe-steps` | | **Участник B** | Оценки, комментарии, избранное, дашборд, UI | `feature-ingredients`, `feature-recipe-ingredients-m2m`, `feature-ratings` | ### Распределение модулей (для 3 человек) | Участник | Модуль | Ветки | |---|---|---| | **Участник A** | Рецепты, категории, шаги приготовления, загрузка изображений | `feature/recipes-crud`, `feature/categories`, `feature-recipe-steps` | | **Участник B** | Ингредиенты, многие-ко-многим, оценки, расчёт рейтинга | `feature-ingredients`, `feature-recipe-ingredients-m2m`, `feature-ratings` | | **Участник C** | Комментарии, избранное, каталог, UI, дашборд | `feature-comments`, `feature-favorites`, `feature-recipe-catalog-ui` | ### Правила 1. Ветка `develop` — основная ветка разработки 2. Каждый участник создаёт фич-ветки от `develop` 3. Минимум 3 PR на участника 4. Обязательный код-ревью перед мёржем 5. Conventional Commits ### Визуализация workflow (2 участника) ```mermaid gitGraph commit id: "init" tag: "v1.0" branch develop checkout develop branch feature/recipes-crud checkout develop branch feature-ingredients checkout feature/recipes-crud commit id: "feat: add recipes CRUD" commit id: "feat: add recipe steps" checkout feature-ingredients commit id: "feat: add ingredients m2m" commit id: "feat: add ratings system" checkout develop merge feature/recipes-crud tag: "PR + review" merge feature-ingredients tag: "PR + review" checkout main merge develop tag: "release v1.1" ``` ### Визуализация workflow (3 участника) ```mermaid gitGraph commit id: "init" tag: "v1.0" branch develop checkout develop branch feature/recipes-crud checkout develop branch feature-ingredients checkout develop branch feature-comments checkout feature/recipes-crud commit id: "feat: add recipes CRUD" commit id: "feat: add recipe categories" checkout feature-ingredients commit id: "feat: add ingredients m2m" commit id: "feat: add ratings system" checkout feature-comments commit id: "feat: add recipe comments" commit id: "feat: add favorites system" checkout develop merge feature/recipes-crud tag: "PR + review" merge feature-ingredients tag: "PR + review" merge feature-comments tag: "PR + review" checkout main merge develop tag: "release v1.1" ``` --- ## 8. Критерии приёмки ### Обязательно - [ ] CRUD рецептов с загрузкой изображений - [ ] CRUD категорий рецептов - [ ] Шаги приготовления (CRUD внутри рецепта) - [ ] Справочник ингредиентов (CRUD, только админ) - [ ] Привязка ингредиентов к рецептам (многие-ко-многим с количеством и единицей) - [ ] Оценка рецепта (1–5 звёзд, один голос на пользователя) - [ ] Автоматический расчёт среднего рейтинга - [ ] Комментарии к рецептам (создание + удаление) - [ ] Избранное (добавление/удаление) - [ ] Панель автора (мои рецепты, статистика) - [ ] Policies: пользователь не может редактировать чужие рецепты - [ ] Адаптивная Bootstrap-вёрстка - [ ] Flash-сообщения - [ ] Пагинация каталога и комментариев - [ ] Git-история: минимум 3 ветки на участника, PR с ревью ### Дополнительно (бонусные баллы) - [ ] Поиск рецептов по названию и ингредиентам - [ ] Фильтрация по времени приготовления и сложности - [ ] Сортировка по рейтингу, дате, времени приготовления - [ ] Печать рецепта (кнопка «Распечатать» → print-friendly страница) - [ ] Мягкое удаление рецептов и комментариев - [ ] Тесты: PHPUnit Feature-тесты для RatingController - [ ] API: RESTful API для каталога рецептов --- ## 9. Дополнительные задания (для продвинутых) 1. **Масштабирование рецепта:** калькулятор для пересчёта ингредиентов на другое количество порций 2. **Планировщик питания:** календарь с рецептами на неделю 3. **Коллекции:** пользовательские подборки рецептов (как плейлисты) 4. **Тесты:** PHPUnit Feature-тесты для RecipeController и RatingController 5. **API:** RESTful API для каталога и создания рецептов 6. **Queue:** асинхронный пересчёт рейтинга при большом количестве оценок 7. **Events & Listeners:** событие «новая оценка» → пересчёт рейтинга --- ## 10. Рекомендуемые материалы - Laravel Docs: https://laravel.com/docs/12.x - Laravel Many-to-Many with Pivot: https://laravel.com/docs/12.x/eloquent-relationships#many-to-many - Bootstrap 5 Cards: https://getbootstrap.com/docs/5.3/components/card/ - Laravel Aggregates: https://laravel.com/docs/12.x/queries#aggregates - Laravel Policies: https://laravel.com/docs/12.x/authorization#creating-policies