Техническое задание: Платформа для обмена книгами (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. Маршруты и контроллеры
// Публичные маршруты
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
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 |
Правила
- Ветка
develop — основная ветка разработки
- Каждый участник создаёт фич-ветки от
develop
- Минимум 3 PR на участника
- Обязательный код-ревью перед мёржем
- Conventional Commits
Визуализация workflow (2 участника)
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 участника)
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. Критерии приёмки
Обязательно
Дополнительно (бонусные баллы)
9. Дополнительные задания (для продвинутых)
- Уведомления: email и on-site уведомления при новых предложениях, ответах, wishlist-совпадениях
- Совпадения Wishlist: автоматическое определение, когда книга одного пользователя есть в wishlist другого
- Множественный обмен: предложение нескольких книг взамен
- Тесты: PHPUnit Feature-тесты для ExchangeController (статусные переходы)
- API: RESTful API для каталога и создания предложений
- Queue: асинхронная отправка email-уведомлений
- Events & Listeners: событие «обмен завершён» → напоминание оставить отзыв
10. Рекомендуемые материалы