laravel-12-tech-specs/task07-recipe-catalog.md

401 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# Техническое задание: Каталог рецептов с рейтингом
## 1. Описание проекта
Кулинарная платформа для публикации рецептов, комментирования и оценки блюд. Проект закрепляет навыки CRUD, отношений многие-ко-многим (рецепт ↔ ингредиенты), расчёта среднего рейтинга, разграничения прав и совместной работы через Git.
**Команда:** 23 человека
---
## 2. Функциональные требования
### 2.1. Аутентификация и регистрация
- Регистрация, вход, выход (Laravel Breeze)
### 2.2. Каталог рецептов
- Просмотр списка рецептов с пагинацией
- Фильтрация по категориям (завтрак, обед, ужин, выпечка, десерты, напитки)
- Поиск по названию и ингредиентам
- Сортировка по дате добавления, рейтингу, времени приготовления
- Детальная страница рецепта
### 2.3. Управление рецептами (автор/админ)
- CRUD рецептов: название, описание, категория, время приготовления, количество порций, изображение, сложность
- Пошаговое приготовление (несколько шагов, каждое — отдельная запись)
- Привязка ингредиентов с количеством и единицей измерения
- Загрузка изображения рецепта (валидация: jpg/png, макс. 2MB)
### 2.4. Ингредиенты
- Справочник ингредиентов (название, единица измерения по умолчанию)
- CRUD ингредиентов (админ)
- Привязка к рецептам (многие-ко-многим) с указанием количества и единицы
### 2.5. Комментарии
- Добавление комментариев к рецепту
- Удаление своего комментария
- Удаление любого комментария (админ/автор рецепта)
- Пагинация комментариев
### 2.6. Оценки и рейтинг
- Оценка рецепта (15 звёзд) — один голос на пользователя
- Отображение среднего рейтинга на карточке и странице рецепта
- Автоматический пересчёт среднего рейтинга
### 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.005.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 | Оценка (15) |
| 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, только админ)
- [ ] Привязка ингредиентов к рецептам (многие-ко-многим с количеством и единицей)
- [ ] Оценка рецепта (15 звёзд, один голос на пользователя)
- [ ] Автоматический расчёт среднего рейтинга
- [ ] Комментарии к рецептам (создание + удаление)
- [ ] Избранное (добавление/удаление)
- [ ] Панель автора (мои рецепты, статистика)
- [ ] 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