laravel-12-tech-specs/task09-book-exchange.md

389 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.

# Техническое задание: Платформа для обмена книгами (Book Exchange)
## 1. Описание проекта
Веб-сервис для обмена книгами между пользователями с системой Wishlist, обменными предложениями и отзывами. Проект закрепляет навыки CRUD, статусных переходов, работы с отношениями многие-ко-многим, разграничения прав и совместной работы через Git.
**Команда:** 23 человека
---
## 2. Функциональные требования
### 2.1. Аутентификация и регистрация
- Регистрация, вход, выход (Laravel Breeze)
### 2.2. Каталог книг
- Просмотр списка доступных для обмена книг с пагинацией
- Фильтрация по жанрам/категориям
- Поиск по названию, автору, ISBN
- Детальная страница книги (обложка, описание, владелец, состояние, кнопка «Предложить обмен»)
### 2.3. Управление книгами (владелец)
- CRUD книг: название, автор, ISBN, описание, жанр, обложка, состояние (новая, хорошая, удовлетворительная)
- Загрузка обложки книги (валидация: jpg/png, макс. 2MB)
- Статус книги: доступна для обмена / зарезервирована / обмен состоялась
- Мои книги (личный кабинет владельца)
### 2.4. Wishlist (список желаемого)
- Добавление книг в wishlist: название, автор (свободный ввод)
- Уведомление владельцу книги из wishlist, если кто-то её добавил
- Страница «Мой Wishlist»
### 2.5. Обменные предложения
- Пользователь создаёт предложение обмена: книга владельца → книга взамен (из книг отправителя) + комментарий
- Статусы обмена: предложено, принято, отклонено, завершено, отменено
- Владелец книги принимает или отклоняет предложение
- При принятии — обе книги получают статус «обмен состоялась»
- При отмене — возврат статусов «доступна для обмена»
- Отзывы после завершения обмена
### 2.6. Отзывы об обмене
- Оба участника оставляют отзыв друг другу после завершения обмена
- Поля отзыва: текст, рейтинг (15 звёзд)
- Средний рейтинг пользователя
- Отзывы на странице профиля
### 2.7. Панель пользователя
- Мои книги (созданные мной)
- Мои обменные предложения (отправленные и полученные)
- Мой Wishlist
- Мои отзывы (полученные и оставленные)
- Профиль и рейтинг
### 2.8. Панель администратора
- Управление пользователями и ролями
- Модерация книг и отзывов
- Общая статистика платформы
---
## 3. Структура базы данных
### Таблица `users`
| Поле | Тип | Описание |
|---|---|---|
| id | BIGINT UNSIGNED | Первичный ключ |
| name | VARCHAR(255) | Имя |
| email | VARCHAR(255) | Email (unique) |
| password | VARCHAR(255) | Хеш пароля |
| role | ENUM('admin', 'owner', 'requester') | Роль |
| avatar | VARCHAR(255) \| NULL | Аватар |
| rating | DECIMAL(3, 2) \| NULL | Средний рейтинг (0.005.00) |
| created_at | TIMESTAMP | |
| updated_at | TIMESTAMP | |
### Таблица `genres`
| Поле | Тип | Описание |
|---|---|---|
| id | BIGINT UNSIGNED | Первичный ключ |
| name | VARCHAR(255) | Название жанра |
| slug | VARCHAR(255) | URL-идентификатор (unique) |
| created_at | TIMESTAMP | |
| updated_at | TIMESTAMP | |
### Таблица `books`
| Поле | Тип | Описание |
|---|---|---|
| id | BIGINT UNSIGNED | Первичный ключ |
| title | VARCHAR(255) | Название книги |
| author | VARCHAR(255) | Автор |
| isbn | VARCHAR(20) \| NULL | ISBN |
| description | TEXT \| NULL | Описание |
| cover_image | VARCHAR(255) \| NULL | Обложка |
| genre_id | BIGINT UNSIGNED | Жанр (FK → genres) |
| owner_id | BIGINT UNSIGNED | Владелец (FK → users) |
| condition | ENUM('new', 'good', 'fair') | Состояние |
| status | ENUM('available', 'reserved', 'exchanged') | Статус |
| created_at | TIMESTAMP | |
| updated_at | TIMESTAMP | |
### Таблица `wishlists`
| Поле | Тип | Описание |
|---|---|---|
| id | BIGINT UNSIGNED | Первичный ключ |
| user_id | BIGINT UNSIGNED | FK → users |
| title | VARCHAR(255) | Желаемая книга (название) |
| author | VARCHAR(255) \| NULL | Желаемый автор |
| created_at | TIMESTAMP | |
| updated_at | TIMESTAMP | |
### Таблица `exchange_offers`
| Поле | Тип | Описание |
|---|---|---|
| id | BIGINT UNSIGNED | Первичный ключ |
| requester_id | BIGINT UNSIGNED | Инициатор обмена (FK → users) |
| owner_book_id | BIGINT UNSIGNED | Книга владельца (FK → books) |
| offered_book_id | BIGINT UNSIGNED | Предлагаемая книга взамен (FK → books) |
| message | TEXT \| NULL | Комментарий инициатора |
| status | ENUM('proposed', 'accepted', 'rejected', 'completed', 'cancelled') | Статус |
| response_message | TEXT \| NULL | Ответ владельца |
| created_at | TIMESTAMP | |
| updated_at | TIMESTAMP | |
### Таблица `reviews`
| Поле | Тип | Описание |
|---|---|---|
| id | BIGINT UNSIGNED | Первичный ключ |
| exchange_offer_id | BIGINT UNSIGNED | Связанный обмен (FK → exchange_offers) |
| author_id | BIGINT UNSIGNED | Автор отзыва (FK → users) |
| target_user_id | BIGINT UNSIGNED | Получатель отзыва (FK → users) |
| rating | TINYINT | Рейтинг (15) |
| comment | TEXT | Текст отзыва |
| created_at | TIMESTAMP | |
| updated_at | TIMESTAMP | |
---
## 4. Маршруты и контроллеры
```php
// Публичные маршруты
Route::get('/', [BookController::class, 'index'])->name('books.index');
Route::get('/books/{book}', [BookController::class, 'show'])->name('books.show');
// Аутентифицированные маршруты
Route::middleware('auth')->group(function () {
// Wishlist
Route::resource('wishlist', WishlistController::class);
// Обменные предложения
Route::prefix('exchanges')->name('exchanges.')->group(function () {
Route::post('/offer/{book}', [ExchangeController::class, 'store'])->name('offer');
Route::get('/sent', [ExchangeController::class, 'sent'])->name('sent');
Route::get('/received', [ExchangeController::class, 'received'])->name('received');
Route::get('/{exchange}', [ExchangeController::class, 'show'])->name('show');
Route::post('/{exchange}/accept', [ExchangeController::class, 'accept'])->name('accept');
Route::post('/{exchange}/reject', [ExchangeController::class, 'reject'])->name('reject');
Route::post('/{exchange}/complete', [ExchangeController::class, 'complete'])->name('complete');
Route::post('/{exchange}/cancel', [ExchangeController::class, 'cancel'])->name('cancel');
});
// Отзывы
Route::resource('reviews', ReviewController::class)->only(['store', 'destroy']);
// Мои книги (владелец)
Route::prefix('my-books')->name('my-books.')->group(function () {
Route::resource('books', BookController::class);
Route::get('dashboard', [DashboardController::class, 'index'])->name('dashboard');
});
// Админка
Route::prefix('admin')->name('admin.')->middleware('can:access-admin-panel')->group(function () {
Route::resource('users', AdminUserController::class);
Route::resource('genres', AdminGenreController::class);
Route::patch('books/{book}/toggle-status', [AdminBookController::class, 'toggleStatus'])->name('books.status');
Route::patch('reviews/{review}/toggle-publish', [AdminReviewController::class, 'togglePublish'])->name('reviews.publish');
Route::get('dashboard', [AdminDashboardController::class, 'index'])->name('dashboard');
});
});
```
### Контроллеры
- `BookController` — публичный каталог + CRUD книг для владельца
- `WishlistController` — CRUD желаемых книг
- `ExchangeController` — создание и управление обменными предложениями
- `ReviewController` — создание/удаление отзывов
- `DashboardController` — панель пользователя
- `AdminUserController`, `AdminGenreController`, `AdminBookController`, `AdminReviewController`, `AdminDashboardController` — админка
---
## 5. Роли, Gates и Policies
### Роли
| Роль | Описание |
|---|---|
| `admin` | Полный доступ, модерация книг и отзывов, управление пользователями |
| `owner` | Добавление книг, управление обменными предложениями, подтверждение обмена |
| `requester` | Просмотр каталога, создание запросов на обмен, Wishlist |
> **Примечание:** при регистрации пользователь получает обе роли `owner` и `requester` (фактически — роль `user`). Разделение логическое, а не через middleware.
### Gates
```php
Gate::define('access-admin-panel', function (User $user) {
return $user->role === 'admin';
});
```
### Policies
**BookPolicy:**
- `view` — все (публичный каталог)
- `create` — все аутентифицированные
- `update`, `delete` — владелец книги или admin
- `offer` — не владелец этой книги (и не предлагает свою же)
**ExchangeOfferPolicy:**
- `create` — все аутентифицированные (не владелец целевой книги)
- `view` — инициатор, владелец книги или admin
- `accept`, `reject` — владелец целевой книги
- `complete`, `cancel` — инициатор или владелец (до завершения)
**WishlistPolicy:**
- `create`, `update`, `delete` — владелец wishlist или admin
- `view` — только владелец wishlist
**ReviewPolicy:**
- `create` — участник завершённого обмена (ещё не оставил отзыв)
- `view` — все (если опубликован), автор или admin (если не опубликован)
- `delete` — автор отзыва или admin
---
## 6. Требования к интерфейсу (Bootstrap 5)
### Компоненты
- **Навбар** — логотип, каталог, «Мои книги», «Обмены», «Wishlist», профиль
- **Карточки книг** — обложка, название, автор, жанр (бейдж), состояние (бейдж), владелец
- **Страница книги** — большая обложка, описание, автор, ISBN, состояние, кнопка «Предложить обмен»
- **Форма обменного предложения** — выбор своей книги взамен (dropdown), комментарий
- **Таблица обменных предложений** — книги, статус (цветные бейджи), действия
- **Звёздный рейтинг** — для отзывов
- **Wishlist** — список желаемых книг (таблица/карточки)
- **Дашборд** — статистика (карточки), активные обмены, wishlist-совпадения
- **Адаптивная сетка** — `col-md-4` для карточек, `col-md-8` + `col-md-4` для основного контента
### Цветовая схема статусов обмена
- Предложено — `bg-info`
- Принято — `bg-success`
- Отклонено — `bg-danger`
- Завершено — `bg-primary`
- Отменено — `bg-secondary`
### Цветовая схема состояния книги
- Новая — `bg-success`
- Хорошая — `bg-primary`
- Удовлетворительная — `bg-warning`
---
## 7. Git-workflow для команды
### Распределение модулей (для 2 человек)
| Участник | Модуль | Ветки |
|---|---|---|
| **Участник A** | Книги, жанры, обложки, обменные предложения | `feature/books-crud`, `feature-genres`, `feature-book-covers` |
| **Участник B** | Wishlist, отзывы, рейтинги, дашборд, UI | `feature-exchange-offers`, `feature-offer-statuses`, `feature-offer-validation` |
### Распределение модулей (для 3 человек)
| Участник | Модуль | Ветки |
|---|---|---|
| **Участник A** | Книги, жанры, загрузка обложек, каталог | `feature/books-crud`, `feature-genres`, `feature-book-covers` |
| **Участник B** | Обменные предложения, статусы, валидация | `feature-exchange-offers`, `feature-offer-statuses`, `feature-offer-validation` |
| **Участник C** | Wishlist, отзывы, рейтинги, UI, дашборд | `feature-wishlist`, `feature-reviews`, `feature-dashboard-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/books-crud
checkout develop
branch feature-exchange-offers
checkout feature/books-crud
commit id: "feat: add books CRUD"
commit id: "feat: add book covers"
checkout feature-exchange-offers
commit id: "feat: add exchange offers"
commit id: "feat: add offer statuses"
checkout develop
merge feature/books-crud tag: "PR + review"
merge feature-exchange-offers 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/books-crud
checkout develop
branch feature-exchange-offers
checkout develop
branch feature-wishlist
checkout feature/books-crud
commit id: "feat: add books CRUD"
commit id: "feat: add genres CRUD"
checkout feature-exchange-offers
commit id: "feat: add exchange offers"
commit id: "feat: add offer statuses"
checkout feature-wishlist
commit id: "feat: add wishlist CRUD"
commit id: "feat: add reviews system"
checkout develop
merge feature/books-crud tag: "PR + review"
merge feature-exchange-offers tag: "PR + review"
merge feature-wishlist tag: "PR + review"
checkout main
merge develop tag: "release v1.1"
```
---
## 8. Критерии приёмки
### Обязательно
- [ ] CRUD книг с загрузкой обложек
- [ ] CRUD жанров
- [ ] Каталог книг с пагинацией и фильтрацией по жанрам
- [ ] Wishlist (добавление, просмотр, удаление)
- [ ] Создание обменного предложения (книга владельца → книга взамен)
- [ ] Принятие/отклонение предложения владельцем
- [ ] При принятии — обновление статусов обеих книг на «exchanged»
- [ ] При отмене — возврат статусов «available»
- [ ] Отзывы после завершения обмена (оба участника)
- [ ] Расчёт среднего рейтинга пользователя
- [ ] Policies: пользователь не может управлять чужими книгами и предложениями
- [ ] Адаптивная Bootstrap-вёрстка
- [ ] Flash-сообщения
- [ ] Git-история: минимум 3 ветки на участника, PR с ревью
### Дополнительно (бонусные баллы)
- [ ] Поиск книг по названию, автору, ISBN
- [ ] Уведомления владельцу книги из wishlist
- [ ] Уведомления (email при новом предложении / ответе)
- [ ] Рекомендации книг (по жанру, wishlist)
- [ ] Мягкое удаление книг и отзывов
- [ ] Тесты: PHPUnit Feature-тесты для ExchangeController
- [ ] API: RESTful API для каталога книг
---
## 9. Дополнительные задания (для продвинутых)
1. **Уведомления:** email и on-site уведомления при новых предложениях, ответах, wishlist-совпадениях
2. **Совпадения Wishlist:** автоматическое определение, когда книга одного пользователя есть в wishlist другого
3. **Множественный обмен:** предложение нескольких книг взамен
4. **Тесты:** PHPUnit Feature-тесты для ExchangeController (статусные переходы)
5. **API:** RESTful API для каталога и создания предложений
6. **Queue:** асинхронная отправка email-уведомлений
7. **Events & Listeners:** событие «обмен завершён» → напоминание оставить отзыв
---
## 10. Рекомендуемые материалы
- Laravel Docs: https://laravel.com/docs/12.x
- Laravel Many-to-Many: https://laravel.com/docs/12.x/eloquent-relationships#many-to-many
- Bootstrap 5 Badges: https://getbootstrap.com/docs/5.3/components/badge/
- Laravel Policies: https://laravel.com/docs/12.x/authorization#creating-policies
- State Machines (статусные переходы): https://github.com/brentroose/laravel-state-machine