laravel-12-tech-specs/task05-freelance-portfolio.md

385 lines
18 KiB
Markdown
Raw Permalink 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.

# Техническое задание: Портфолио-платформа для фрилансеров
## 1. Описание проекта
Веб-сервис для публикации портфолио фрилансеров, поиска заказов и обмена отзывами. Проект закрепляет навыки CRUD, работы с рейтингами и отзывами, разграничения прав и совместной работы через Git.
**Команда:** 23 человека
---
## 2. Функциональные требования
### 2.1. Аутентификация и регистрация
- Регистрация, вход, выход (Laravel Breeze)
- При регистрации выбирается роль: фрилансер или клиент
### 2.2. Профиль фрилансера
- Публичный профиль: имя, фото, описание, навыки, контакты, рейтинг
- Редактирование профиля (только владелец)
- Загрузка аватара (валидация: jpg/png, макс. 2MB)
- Список навыков (свободный ввод или выбор из тегов)
### 2.3. Портфолио-проекты
- CRUD проектов в портфолио: название, описание, изображения (до 5), технологии/навыки, ссылка на проект
- Привязка проекта к навыкам (многие-ко-многим)
- Публичный просмотр на странице профиля
- Сортировка по дате, рейтингу
### 2.4. Каталог фрилансеров
- Просмотр списка фрилансеров с пагинацией
- Фильтрация по навыкам/категориям услуг
- Поиск по имени и описанию
- Карточка фрилансера (аватар, имя, рейтинг, основные навыки)
### 2.5. Заявки на проекты (клиент → фрилансер)
- Клиент создаёт заявку: описание проекта, бюджет, срок, требуемые навыки
- Привязка заявки к фрилансеру
- Фрилансер видит входящие заявки
- Фрилансер отвечает на заявку (принять / отклонить с комментарием)
- Статусы заявки: новая, принята, отклонена, завершена
### 2.6. Отзывы и рейтинги
- Клиент оставляет отзыв фрилансеру после завершения заявки
- Поля отзыва: текст, рейтинг (15 звёзд)
- Средний рейтинг фрилансера (автоматический пересчёт)
- Отзывы отображаются на странице профиля
### 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.005.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 | Рейтинг (15) |
| 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