# Техническое задание: Платформа для обмена книгами (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