389 lines
19 KiB
Markdown
389 lines
19 KiB
Markdown
# Техническое задание: Платформа для обмена книгами (Book Exchange)
|
||
|
||
## 1. Описание проекта
|
||
|
||
Веб-сервис для обмена книгами между пользователями с системой Wishlist, обменными предложениями и отзывами. Проект закрепляет навыки CRUD, статусных переходов, работы с отношениями многие-ко-многим, разграничения прав и совместной работы через Git.
|
||
|
||
**Команда:** 2–3 человека
|
||
|
||
---
|
||
|
||
## 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. Отзывы об обмене
|
||
- Оба участника оставляют отзыв друг другу после завершения обмена
|
||
- Поля отзыва: текст, рейтинг (1–5 звёзд)
|
||
- Средний рейтинг пользователя
|
||
- Отзывы на странице профиля
|
||
|
||
### 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.00–5.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 | Рейтинг (1–5) |
|
||
| 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
|