# Техническое задание: Портфолио-платформа для фрилансеров ## 1. Описание проекта Веб-сервис для публикации портфолио фрилансеров, поиска заказов и обмена отзывами. Проект закрепляет навыки CRUD, работы с рейтингами и отзывами, разграничения прав и совместной работы через Git. **Команда:** 2–3 человека --- ## 2. Функциональные требования ### 2.1. Аутентификация и регистрация - Регистрация, вход, выход (Laravel Breeze) - При регистрации выбирается роль: фрилансер или клиент ### 2.2. Профиль фрилансера - Публичный профиль: имя, фото, описание, навыки, контакты, рейтинг - Редактирование профиля (только владелец) - Загрузка аватара (валидация: jpg/png, макс. 2MB) - Список навыков (свободный ввод или выбор из тегов) ### 2.3. Портфолио-проекты - CRUD проектов в портфолио: название, описание, изображения (до 5), технологии/навыки, ссылка на проект - Привязка проекта к навыкам (многие-ко-многим) - Публичный просмотр на странице профиля - Сортировка по дате, рейтингу ### 2.4. Каталог фрилансеров - Просмотр списка фрилансеров с пагинацией - Фильтрация по навыкам/категориям услуг - Поиск по имени и описанию - Карточка фрилансера (аватар, имя, рейтинг, основные навыки) ### 2.5. Заявки на проекты (клиент → фрилансер) - Клиент создаёт заявку: описание проекта, бюджет, срок, требуемые навыки - Привязка заявки к фрилансеру - Фрилансер видит входящие заявки - Фрилансер отвечает на заявку (принять / отклонить с комментарием) - Статусы заявки: новая, принята, отклонена, завершена ### 2.6. Отзывы и рейтинги - Клиент оставляет отзыв фрилансеру после завершения заявки - Поля отзыва: текст, рейтинг (1–5 звёзд) - Средний рейтинг фрилансера (автоматический пересчёт) - Отзывы отображаются на странице профиля ### 2.7. Панель фрилансера - Мой профиль (редактирование) - Мои проекты портфолио - Входящие заявки (статусы, ответы) - Мой рейтинг и отзывы ### 2.8. Панель клиента - Мои заявки (статусы) - Создание новой заявки (выбор фрилансера) - Оставить отзыв (после завершения) ### 2.9. Панель администратора - Управление пользователями - Модерация отзывов (скрыть/удалить) - Общая статистика --- ## 3. Структура базы данных ### Таблица `users` | Поле | Тип | Описание | |---|---|---| | id | BIGINT UNSIGNED | Первичный ключ | | name | VARCHAR(255) | Имя | | email | VARCHAR(255) | Email (unique) | | password | VARCHAR(255) | Хеш пароля | | role | ENUM('admin', 'freelancer', 'client') | Роль | | bio | TEXT \| NULL | О себе | | avatar | VARCHAR(255) \| NULL | Аватар | | rating | DECIMAL(3, 2) \| NULL | Средний рейтинг (0.00–5.00) | | created_at | TIMESTAMP | | | updated_at | TIMESTAMP | | ### Таблица `skills` | Поле | Тип | Описание | |---|---|---| | id | BIGINT UNSIGNED | Первичный ключ | | name | VARCHAR(255) | Название навыка (unique) | | slug | VARCHAR(255) | URL-идентификатор (unique) | | created_at | TIMESTAMP | | | updated_at | TIMESTAMP | | ### Таблица `freelancer_skill` (многие-ко-многим) | Поле | Тип | Описание | |---|---|---| | freelancer_id | BIGINT UNSIGNED | FK → users | | skill_id | BIGINT UNSIGNED | FK → skills | ### Таблица `portfolio_projects` | Поле | Тип | Описание | |---|---|---| | id | BIGINT UNSIGNED | Первичный ключ | | freelancer_id | BIGINT UNSIGNED | FK → users | | title | VARCHAR(255) | Название проекта | | description | TEXT | Описание | | project_url | VARCHAR(255) \| NULL | Ссылка на проект | | created_at | TIMESTAMP | | | updated_at | TIMESTAMP | | ### Таблица `portfolio_skill` (многие-ко-многим) | Поле | Тип | Описание | |---|---|---| | portfolio_project_id | BIGINT UNSIGNED | FK → portfolio_projects | | skill_id | BIGINT UNSIGNED | FK → skills | ### Таблица `project_proposals` | Поле | Тип | Описание | |---|---|---| | id | BIGINT UNSIGNED | Первичный ключ | | client_id | BIGINT UNSIGNED | Клиент (FK → users) | | freelancer_id | BIGINT UNSIGNED | Фрилансер (FK → users) | | title | VARCHAR(255) | Название заявки | | description | TEXT | Описание проекта | | budget | DECIMAL(10, 2) \| NULL | Бюджет | | deadline | DATE \| NULL | Срок выполнения | | status | ENUM('new', 'accepted', 'rejected', 'completed') | Статус | | response_message | TEXT \| NULL | Ответ фрилансера | | created_at | TIMESTAMP | | | updated_at | TIMESTAMP | | ### Таблица `reviews` | Поле | Тип | Описание | |---|---|---| | id | BIGINT UNSIGNED | Первичный ключ | | client_id | BIGINT UNSIGNED | Автор отзыва (FK → users) | | freelancer_id | BIGINT UNSIGNED | Получатель отзыва (FK → users) | | proposal_id | BIGINT UNSIGNED \| NULL | Связанная заявка (FK → project_proposals) | | rating | TINYINT | Рейтинг (1–5) | | comment | TEXT | Текст отзыва | | is_published | BOOLEAN | Опубликован ли | | created_at | TIMESTAMP | | | updated_at | TIMESTAMP | | --- ## 4. Маршруты и контроллеры ```php // Публичные маршруты Route::get('/', [FreelancerController::class, 'index'])->name('freelancers.index'); Route::get('/freelancers/{user}', [FreelancerController::class, 'show'])->name('freelancers.show'); // Аутентифицированные маршруты Route::middleware('auth')->group(function () { // Профиль Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit'); Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update'); // Портфолио (фрилансер) Route::prefix('my-portfolio')->name('portfolio.')->middleware('can:be-freelancer')->group(function () { Route::resource('projects', PortfolioProjectController::class); Route::get('dashboard', [FreelancerDashboardController::class, 'index'])->name('dashboard'); }); // Заявки Route::prefix('proposals')->name('proposals.')->group(function () { Route::post('/send/{freelancer}', [ProposalController::class, 'store'])->name('send'); Route::get('/my-sent', [ProposalController::class, 'mySent'])->name('my-sent'); // клиент Route::get('/incoming', [ProposalController::class, 'incoming'])->name('incoming'); // фрилансер Route::post('/{proposal}/accept', [ProposalController::class, 'accept'])->name('accept'); Route::post('/{proposal}/reject', [ProposalController::class, 'reject'])->name('reject'); }); // Отзывы Route::resource('reviews', ReviewController::class)->only(['store', 'update', 'destroy']); // Админка Route::prefix('admin')->name('admin.')->middleware('can:access-admin-panel')->group(function () { Route::resource('users', AdminUserController::class); Route::resource('skills', AdminSkillController::class); Route::patch('reviews/{review}/toggle-publish', [AdminReviewController::class, 'togglePublish'])->name('reviews.publish'); Route::get('dashboard', [AdminDashboardController::class, 'index'])->name('dashboard'); }); }); ``` ### Контроллеры - `FreelancerController` — публичный каталог и профиль - `ProfileController` — редактирование профиля - `PortfolioProjectController` — CRUD проектов портфолио - `ProposalController` — создание и управление заявками - `ReviewController` — создание/удаление отзывов - `FreelancerDashboardController` — панель фрилансера - `AdminUserController`, `AdminSkillController`, `AdminReviewController`, `AdminDashboardController` — админка --- ## 5. Роли, Gates и Policies ### Роли | Роль | Описание | |---|---| | `admin` | Полный доступ, модерация отзывов, управление пользователями | | `freelancer` | Управление профилем, портфолио, просмотр входящих заявок, ответы | | `client` | Создание заявок, просмотр каталога, написание отзывов | ### Gates ```php Gate::define('be-freelancer', function (User $user) { return in_array($user->role, ['admin', 'freelancer']); }); Gate::define('be-client', function (User $user) { return in_array($user->role, ['admin', 'client']); }); Gate::define('access-admin-panel', function (User $user) { return $user->role === 'admin'; }); ``` ### Policies **PortfolioProjectPolicy:** - `view` — все (публичное портфолио) - `create`, `update`, `delete` — только владелец портфолио или admin **ProposalPolicy:** - `create` — client - `view` — клиент (отправитель), фрилансер (получатель), admin - `accept`, `reject` — только фрилансер (получатель) **ReviewPolicy:** - `create` — client (после завершения заявки) - `view` — все (если опубликован), автор или admin (если не опубликован) - `delete` — автор отзыва или admin --- ## 6. Требования к интерфейсу (Bootstrap 5) ### Компоненты - **Навбар** — логотип, каталог фрилансеров, панель (мои заявки / портфолио), профиль - **Карточки фрилансеров** — аватар (круглый), имя, рейтинг (звёзды), навыки (бейджи) - **Профиль фрилансера** — большое фото, описание, навыки (бейджи), проекты портфолио (карточки), отзывы - **Звёздный рейтинг** — интерактивные звёзды (radio buttons + стилизация или простой JS) - **Карточки проектов портфолио** — изображение, название, описание, технологии (бейджи) - **Форма заявки** — описание, бюджет (число), срок (дата), требуемые навыки - **Таблица заявок** — статусы (цветные бейджи), действия (принять/отклонить) - **Дашборд фрилансера** — статистика (карточки), входящие заявки, рейтинг - **Адаптивная сетка** — `col-md-4` для карточек, `col-md-3` + `col-md-9` для профиля ### Цветовая схема статусов заявок - Новая — `bg-info` - Принята — `bg-success` - Отклонена — `bg-danger` - Завершена — `bg-secondary` --- ## 7. Git-workflow для команды ### Распределение модулей (для 2 человек) | Участник | Модуль | Ветки | |---|---|---| | **Участник A** | Профили, навыки, портфолио, каталог | `feature/profiles`, `feature-skills`, `feature-portfolio-crud` | | **Участник B** | Заявки, отзывы, рейтинги, UI | `feature/proposals`, `feature-proposal-responses`, `feature-proposal-statuses` | ### Распределение модулей (для 3 человек) | Участник | Модуль | Ветки | |---|---|---| | **Участник A** | Профили, навыки, портфолио | `feature/profiles`, `feature-skills`, `feature-portfolio-crud` | | **Участник B** | Заявки, статусы, ответы фрилансера | `feature/proposals`, `feature-proposal-responses`, `feature-proposal-statuses` | | **Участник C** | Отзывы, рейтинги, каталог фрилансеров, UI | `feature-reviews`, `feature-ratings`, `feature-freelancer-catalog` | ### Правила 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/profiles checkout develop branch feature/proposals checkout feature/profiles commit id: "feat: add profiles and skills" commit id: "feat: add portfolio CRUD" checkout feature/proposals commit id: "feat: add proposals system" commit id: "feat: add proposal responses" checkout develop merge feature/profiles tag: "PR + review" merge feature/proposals 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/profiles checkout develop branch feature/proposals checkout develop branch feature-reviews checkout feature/profiles commit id: "feat: add profiles and skills" commit id: "feat: add portfolio images" checkout feature/proposals commit id: "feat: add proposals system" commit id: "feat: add proposal responses" checkout feature-reviews commit id: "feat: add reviews and ratings" commit id: "feat: add freelancer catalog" checkout develop merge feature/profiles tag: "PR + review" merge feature/proposals tag: "PR + review" merge feature-reviews tag: "PR + review" checkout main merge develop tag: "release v1.1" ``` --- ## 8. Критерии приёмки ### Обязательно - [ ] Регистрация с выбором роли (фрилансер / клиент) - [ ] Редактирование профиля с загрузкой аватара - [ ] CRUD проектов портфолио с изображениями - [ ] Навыки (многие-ко-многим фрилансер ↔ навыки) - [ ] Каталог фрилансеров с пагинацией и фильтрацией по навыкам - [ ] Создание заявки клиентом - [ ] Фрилансер может принять/отклонить заявку - [ ] Оставление отзыва клиентом (после завершения заявки) - [ ] Расчёт среднего рейтинга фрилансера - [ ] Policies: фрилансер не видит чужие входящие заявки, клиент не может управлять чужими заявками - [ ] Адаптивная Bootstrap-вёрстка - [ ] Flash-сообщения - [ ] Git-история: минимум 3 ветки на участника, PR с ревью ### Дополнительно (бонусные баллы) - [ ] Поиск фрилансеров по имени и описанию - [ ] Несколько изображений в проекте портфолио (галерея) - [ ] Сортировка фрилансеров по рейтингу, дате регистрации - [ ] Уведомления (email при новой заявке / ответе) - [ ] Мягкое удаление отзывов - [ ] Тесты: PHPUnit Feature-тесты для ProposalController - [ ] API: RESTful API для каталога фрилансеров --- ## 9. Дополнительные задания (для продвинутых) 1. **Мессенджер:** встроенный чат между клиентом и фрилансером по заявке 2. **Контракт:** генерация PDF-документа с условиями при принятии заявки 3. **Уведомления:** email и on-site уведомления при новых заявках/ответах/отзывах 4. **Фильтры:** расширенная фильтрация (рейтинг, стоимость, навыки) 5. **Тесты:** PHPUnit Feature-тесты для ReviewController и ProposalController 6. **API:** RESTful API для каталога фрилансеров и создания заявок 7. **Queue:** асинхронная отправка email-уведомлений --- ## 10. Рекомендуемые материалы - Laravel Docs: https://laravel.com/docs/12.x - Laravel Many-to-Many: https://laravel.com/docs/12.x/eloquent-relationships#many-to-many - Laravel File Uploads: https://laravel.com/docs/12.x/filesystem#file-uploads - Bootstrap 5 Cards: https://getbootstrap.com/docs/5.3/components/card/ - Laravel Policies: https://laravel.com/docs/12.x/authorization#creating-policies