Fix WebSocket URL handling and simplify .env.local setup
This commit is contained in:
parent
bc20d7e61e
commit
8a7810431d
@ -9,6 +9,5 @@ class ApiConfig(AppConfig):
|
||||
def ready(self):
|
||||
"""Инициализация при загрузке приложения."""
|
||||
# Применяем патч для django-scheduler (исправление бага с byweekday)
|
||||
print("🔧 ApiConfig.ready() - применение патча django-scheduler")
|
||||
import schedule_patch
|
||||
schedule_patch.apply_patch()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -70,8 +70,18 @@ def create_patched_method():
|
||||
return patched_event_params
|
||||
|
||||
|
||||
# Флаг для предотвращения повторного применения патча
|
||||
_patch_applied = False
|
||||
|
||||
|
||||
def apply_patch():
|
||||
"""Применяет патч к django-scheduler Event модели."""
|
||||
global _patch_applied
|
||||
|
||||
# Если патч уже применён, пропускаем
|
||||
if _patch_applied:
|
||||
return True
|
||||
|
||||
try:
|
||||
# Ленивый импорт - только при вызове apply_patch()
|
||||
from schedule.models import Event
|
||||
@ -86,6 +96,7 @@ def apply_patch():
|
||||
print(f" Патч применён: Event._event_params = {patched_method.__name__}")
|
||||
print("=" * 80)
|
||||
|
||||
_patch_applied = True
|
||||
return True
|
||||
except Exception as e:
|
||||
print("=" * 80)
|
||||
|
||||
195
docs/diagnostic/WEBSOCKET_FIX.md
Normal file
195
docs/diagnostic/WEBSOCKET_FIX.md
Normal file
@ -0,0 +1,195 @@
|
||||
# 🔧 Исправление WebSocket ошибки: "❌ WebSocket error: {}"
|
||||
|
||||
## Проблема
|
||||
|
||||
При открытии чата в браузере видна ошибка:
|
||||
```
|
||||
❌ WebSocket error: {}
|
||||
```
|
||||
|
||||
## Причины
|
||||
|
||||
WebSocket ошибка `{}` обычно связана с одной из этих проблем:
|
||||
|
||||
| Причина | Решение |
|
||||
|---------|---------|
|
||||
| **Backend запущен через `runserver`** | Используйте **Daphne** для WebSocket |
|
||||
| **PORT 9000 занят** | Проверьте процессы или используйте другой порт |
|
||||
| **Неправильный URL в .env** | Убедитесь что используется `.env.local` |
|
||||
| **JWT токен не сохранен** | Выполните вход в систему |
|
||||
| **Frontend и Backend на разных версиях** | Синхронизируйте код |
|
||||
|
||||
## ✅ Пошаговое решение
|
||||
|
||||
### Шаг 1: Остановите текущий backend
|
||||
```bash
|
||||
# Если backend запущен, нажмите Ctrl+C в терминале
|
||||
```
|
||||
|
||||
### Шаг 2: Запустите backend через Daphne
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
.venv/Scripts/daphne -b 0.0.0.0 -p 9000 eusrr_backend.asgi:application
|
||||
```
|
||||
|
||||
**Ожидаемый вывод:**
|
||||
```
|
||||
Starting server process [PID]
|
||||
HTTP/2 support not enabled
|
||||
Uvicorn running on http://0.0.0.0:9000
|
||||
```
|
||||
|
||||
### Шаг 3: Убедитесь что .env.local правильный
|
||||
|
||||
Проверьте файл `frontend/.env.local`:
|
||||
```dotenv
|
||||
# ✅ ПРАВИЛЬНО (для локальной разработки)
|
||||
NEXT_PUBLIC_WS_URL=ws://localhost:9000
|
||||
NEXT_PUBLIC_BACKEND_URL=http://localhost:9000
|
||||
```
|
||||
|
||||
**НЕ используйте:**
|
||||
```dotenv
|
||||
# ❌ НЕПРАВИЛЬНО (production URL)
|
||||
NEXT_PUBLIC_WS_URL=wss://corp.robotail.pro
|
||||
```
|
||||
|
||||
### Шаг 4: Перезагрузите фронтенд
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Перезагрузите браузер: `Ctrl+R` или `F5`
|
||||
|
||||
### Шаг 5: Проверьте WebSocket подключение
|
||||
|
||||
Откройте **DevTools** (F12) → **Console** окно и ищите:
|
||||
|
||||
**✅ Если видите:**
|
||||
```
|
||||
🔄 Подключение к WebSocket: ws://localhost:9000/ws/?token=...
|
||||
✅ WebSocket connected!
|
||||
```
|
||||
→ **Всё работает!**
|
||||
|
||||
**❌ Если видите ошибку:**
|
||||
```
|
||||
❌ WebSocket error: {}
|
||||
```
|
||||
→ Продолжайте с Шага 6
|
||||
|
||||
### Шаг 6: Проверьте порт 9000
|
||||
|
||||
Проверьте что процесс слушает на порту 9000:
|
||||
|
||||
**Windows - PowerShell (администратор):**
|
||||
```powershell
|
||||
# Проверить какие процессы используют порт 9000
|
||||
netstat -ano | findstr :9000
|
||||
|
||||
# Пример вывода:
|
||||
# TCP 0.0.0.0:9000 0.0.0.0:0 LISTENING 28904
|
||||
|
||||
# Если что-то там слушает, проверьте что это Daphne
|
||||
tasklist | findstr 28904
|
||||
```
|
||||
|
||||
Если порт занят другим процессом:
|
||||
```bash
|
||||
# Убить процесс в PowerShell
|
||||
taskkill /PID 28904 /F
|
||||
|
||||
# Или используйте другой порт
|
||||
.venv/Scripts/daphne -b 0.0.0.0 -p 9001 eusrr_backend.asgi:application
|
||||
|
||||
# И обновите .env.local
|
||||
NEXT_PUBLIC_WS_URL=ws://localhost:9001
|
||||
```
|
||||
|
||||
### Шаг 7: Проверьте Network в DevTools
|
||||
|
||||
1. Откройте **DevTools** → **Network** вкладка
|
||||
2. В фильтре напишите: `ws`
|
||||
3. Откройте чат и ищите подключение `ws://localhost:9000/ws/`
|
||||
|
||||
**✅ Если видите:**
|
||||
```
|
||||
ws Status: 101 Switching Protocols [WebSocket connected]
|
||||
```
|
||||
|
||||
**❌ Если видите:**
|
||||
```
|
||||
ws Status: Failed [или ConnectError]
|
||||
```
|
||||
→ Backend неправильно запущен
|
||||
|
||||
## 🔄 Полный скрипт для переустановки
|
||||
|
||||
```bash
|
||||
# 1. Перейдите в backend
|
||||
cd backend
|
||||
|
||||
# 2. Установите dependencies если нужно
|
||||
.venv/Scripts/pip install -r requirements.txt
|
||||
|
||||
# 3. Запустите Daphne
|
||||
.venv/Scripts/daphne -b 0.0.0.0 -p 9000 eusrr_backend.asgi:application
|
||||
|
||||
# В другом терминале:
|
||||
|
||||
# 4. Перейдите в frontend
|
||||
cd frontend
|
||||
|
||||
# 5. Установите если нужно
|
||||
npm install
|
||||
|
||||
# 6. Запустите dev server
|
||||
npm run dev
|
||||
|
||||
# 7. Откройте браузер на http://localhost:5173
|
||||
```
|
||||
|
||||
## 📋 Чек-лист для диагностики
|
||||
|
||||
- [ ] Backend запущен через **Daphne** (не `runserver`)
|
||||
- [ ] PORT **9000** свободен и доступен
|
||||
- [ ] `.env.local` использует `ws://localhost:9000`
|
||||
- [ ] JWT токен сохранен в localStorage (пользователь залогирован)
|
||||
- [ ] Frontend запущен на `http://localhost:5173`
|
||||
- [ ] В DevTools → Console видно `🔄 Подключение к WebSocket`
|
||||
- [ ] В DevTools → Network видно WebSocket с статусом **101**
|
||||
|
||||
## 🚨 Если ничего не помогает
|
||||
|
||||
1. **Очистите cache браузера**
|
||||
- F12 → Application → Clear Site Data → ✓ Cookies, ✓ Cache
|
||||
|
||||
2. **Удалите node_modules и переустановите**
|
||||
```bash
|
||||
cd frontend
|
||||
rm -r node_modules package-lock.json
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
3. **Проверьте logs в backend**
|
||||
- В терминале где запущен Daphne должны быть logs подключений
|
||||
- Если их нет - frontend не отправляет запрос
|
||||
|
||||
4. **Обновите код из Git**
|
||||
```bash
|
||||
git pull origin main
|
||||
npm install
|
||||
.venv/Scripts/pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## 📞 Если проблема не решена
|
||||
|
||||
Создайте issue с информацией:
|
||||
- Output из DevTools Console
|
||||
- Output из Daphne терминала
|
||||
- Содержимое `.env.local`
|
||||
- Результат: `netstat -ano | findstr :9000` (Windows)
|
||||
87
docs/in-progress/WEBSOCKET_ERROR_FIX.md
Normal file
87
docs/in-progress/WEBSOCKET_ERROR_FIX.md
Normal file
@ -0,0 +1,87 @@
|
||||
# 📝 Исправление WebSocket ошибки - Резюме
|
||||
|
||||
## 🔧 Что было исправлено
|
||||
|
||||
### 1. **Файл: `frontend/src/hooks/useWebSocket.ts`**
|
||||
|
||||
**Проблема:**
|
||||
- Код неправильно обрабатывал переменную окружения `NEXT_PUBLIC_WS_URL`
|
||||
- Если URL содержал протокол (`ws://localhost:9000`), код добавлял протокол снова → `ws://ws://localhost:9000/ws/`
|
||||
|
||||
**Решение:**
|
||||
- Добавлена проверка: если URL уже содержит протокол - используем его как есть
|
||||
- Если URL это только хост - добавляем протокол
|
||||
- Добавлено логирование: `console.log('🔄 Подключение к WebSocket:', wsUrl)`
|
||||
|
||||
**Измененная логика:**
|
||||
```typescript
|
||||
// ✅ ДО (НЕПРАВИЛЬНО):
|
||||
const host = process.env.NEXT_PUBLIC_WS_URL || 'localhost:9000';
|
||||
const wsUrl = `${protocol}//${host}/ws/?token=${token}`;
|
||||
// Результат: ws://ws://localhost:9000/ws/?token=... ❌
|
||||
|
||||
// ✅ ПОСЛЕ (ПРАВИЛЬНО):
|
||||
if (envWsUrl.startsWith('ws://') || envWsUrl.startsWith('wss://')) {
|
||||
wsUrl = `${envWsUrl}/ws/?token=${token}`;
|
||||
}
|
||||
// Результат: ws://localhost:9000/ws/?token=... ✅
|
||||
```
|
||||
|
||||
### 2. **Файл: `frontend/QUICKSTART.md`**
|
||||
|
||||
**Изменения:**
|
||||
- ✅ Добавлена демонстрация правильного запуска Daphne
|
||||
- ✅ Указано что **НЕ нужно** использовать `runserver` для WebSocket
|
||||
- ✅ Добавлена секция "Проверка WebSocket подключения" с подробной диагностикой
|
||||
- ✅ Расширена секция "Проблемы?" с решением для `WebSocket error: {}`
|
||||
- ✅ Добавлены инструкции по проверке портов в Windows
|
||||
|
||||
### 3. **Файл: `docs/diagnostic/WEBSOCKET_FIX.md`** (новый)
|
||||
|
||||
- 📋 Полная диагностика проблемы с WebSocket
|
||||
- 🔍 Шаг-за-шагом решение проблемы
|
||||
- 🛠️ Скрипты для проверки портов и процессов
|
||||
- ✅ Чек-лист для быстрой проверки
|
||||
|
||||
## 🚀 Что делать дальше
|
||||
|
||||
### 1. Убедитесь что используется `.env.local` (не `.env`)
|
||||
|
||||
```bash
|
||||
# frontend/.env.local (для локальной разработки)
|
||||
NEXT_PUBLIC_WS_URL=ws://localhost:9000
|
||||
NEXT_PUBLIC_BACKEND_URL=http://localhost:9000
|
||||
```
|
||||
|
||||
### 2. Перезапустите backend с Daphne
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
.venv/Scripts/daphne -b 0.0.0.0 -p 9000 eusrr_backend.asgi:application
|
||||
```
|
||||
|
||||
### 3. Перезагрузите frontend
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### 4. Проверьте в DevTools
|
||||
|
||||
- F12 → Console → убедитесь что видите `🔄 Подключение к WebSocket: ws://localhost:9000/...`
|
||||
- F12 → Network → WS → должен быть статус **101 Switching Protocols** ✅
|
||||
|
||||
## 📌 Ключевые моменты
|
||||
|
||||
| Что | ❌ НЕПРАВИЛЬНО | ✅ ПРАВИЛЬНО |
|
||||
|-----|---|---|
|
||||
| Запуск backend | `python manage.py runserver` | `.venv/Scripts/daphne -p 9000 eusrr_backend.asgi:application` |
|
||||
| .env файл | `.env` (production) | `.env.local` (development) |
|
||||
| WebSocket URL | `wss://corp.robotail.pro` | `ws://localhost:9000` |
|
||||
| Логирование | Нет | `🔄 Подключение к WebSocket: ...` |
|
||||
|
||||
## 🔗 Дополнительно
|
||||
|
||||
- 📚 Полная диагностика: [docs/diagnostic/WEBSOCKET_FIX.md](../../docs/diagnostic/WEBSOCKET_FIX.md)
|
||||
- 📖 Quickstart: [frontend/QUICKSTART.md](../QUICKSTART.md)
|
||||
@ -1,6 +1,12 @@
|
||||
# API Configuration
|
||||
VITE_API_URL=http://localhost:8000/api/v2
|
||||
VITE_WS_URL=ws://localhost:8000/ws
|
||||
# Next.js Environment Variables Example
|
||||
# Copy this file to .env.local for local development
|
||||
# Copy this file to .env for production
|
||||
|
||||
# Development server port (optional, Vite uses 5173 by default)
|
||||
# PORT=5173
|
||||
# Backend API URL
|
||||
NEXT_PUBLIC_BACKEND_URL=http://localhost:9000
|
||||
|
||||
# API URL (optional, defaults to BACKEND_URL/api/v1)
|
||||
NEXT_PUBLIC_API_URL=http://localhost:9000/api/v1
|
||||
|
||||
# WebSocket URL (БЕЗ пути /ws/, он добавляется автоматически в коде)
|
||||
NEXT_PUBLIC_WS_URL=ws://localhost:9000
|
||||
|
||||
@ -1,256 +0,0 @@
|
||||
# ✅ API v2 + Frontend Integration Complete!
|
||||
|
||||
## 🎉 Что сделано
|
||||
|
||||
### ✅ Backend - API v2 создан
|
||||
|
||||
**Структура:**
|
||||
```
|
||||
backend/api/v2/
|
||||
├── auth/ ✅ register, verify-email, resend-email
|
||||
├── calendar/ ✅ events, calendars, subscriptions
|
||||
├── communications/ ✅ chats, messages, polls
|
||||
├── documents/ ✅ documents CRUD
|
||||
├── employees/ ✅ employees, departments, positions, skills, groups
|
||||
├── feed/ ✅ posts, comments
|
||||
└── requests/ ✅ requests (заявки)
|
||||
```
|
||||
|
||||
**Все endpoints из v1 портированы в v2** через наследование ViewSet'ов.
|
||||
|
||||
### ✅ Frontend обновлен для v2
|
||||
|
||||
- `.env` → использует `http://localhost:8000/api/v2`
|
||||
- `constants.ts` → default на v2
|
||||
- Все запросы идут в v2 API
|
||||
|
||||
### ✅ Документация создана
|
||||
|
||||
- [API_V2_MIGRATION.md](../../docs/guides/API_V2_MIGRATION.md) - полное описание v2
|
||||
- [QUICKSTART.md](QUICKSTART.md) - обновлен для v2
|
||||
- [DJANGO_CORS_SETUP.md](../../docs/guides/DJANGO_CORS_SETUP.md) - настройка CORS
|
||||
|
||||
---
|
||||
|
||||
## 📋 Полный список endpoints v2
|
||||
|
||||
### Auth (общие для v1/v2)
|
||||
```
|
||||
POST /api/auth/token/ # Login (JWT)
|
||||
POST /api/auth/token/refresh/ # Refresh token
|
||||
POST /api/v2/auth/register/ # Регистрация
|
||||
POST /api/v2/auth/verify-email/ # Подтверждение email
|
||||
POST /api/v2/auth/resend-email/ # Повтор отправки
|
||||
```
|
||||
|
||||
### Employees
|
||||
```
|
||||
GET /api/v2/employees/ # Список
|
||||
POST /api/v2/employees/ # Создать
|
||||
GET /api/v2/employees/{id}/ # Получить
|
||||
PATCH /api/v2/employees/{id}/ # Обновить
|
||||
DELETE /api/v2/employees/{id}/ # Удалить
|
||||
|
||||
GET /api/v2/departments/
|
||||
GET /api/v2/positions/
|
||||
GET /api/v2/skills/
|
||||
GET /api/v2/groups/
|
||||
GET /api/v2/employee-actions/
|
||||
GET /api/v2/department-roles/
|
||||
```
|
||||
|
||||
### Documents
|
||||
```
|
||||
GET /api/v2/documents/
|
||||
POST /api/v2/documents/
|
||||
GET /api/v2/documents/{id}/
|
||||
PATCH /api/v2/documents/{id}/
|
||||
DELETE /api/v2/documents/{id}/
|
||||
```
|
||||
|
||||
### Requests (Заявки)
|
||||
```
|
||||
GET /api/v2/requests/
|
||||
POST /api/v2/requests/
|
||||
GET /api/v2/requests/{id}/
|
||||
PATCH /api/v2/requests/{id}/
|
||||
DELETE /api/v2/requests/{id}/
|
||||
```
|
||||
|
||||
### Calendar
|
||||
```
|
||||
GET /api/v2/calendar/events/
|
||||
POST /api/v2/calendar/events/
|
||||
GET /api/v2/calendar/calendars/
|
||||
GET /api/v2/calendar/subscriptions/
|
||||
```
|
||||
|
||||
### Communications (Чаты)
|
||||
```
|
||||
GET /api/v2/chats/
|
||||
POST /api/v2/chats/
|
||||
GET /api/v2/chats/{id}/
|
||||
GET /api/v2/messages/
|
||||
POST /api/v2/messages/
|
||||
GET /api/v2/polls/
|
||||
```
|
||||
|
||||
### Feed (Лента)
|
||||
```
|
||||
GET /api/v2/posts/
|
||||
POST /api/v2/posts/
|
||||
GET /api/v2/posts/{id}/
|
||||
GET /api/v2/comments/
|
||||
POST /api/v2/comments/
|
||||
```
|
||||
|
||||
### Notifications
|
||||
```
|
||||
GET /api/v2/notifications/ (использует v1 internally)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 v1 vs v2
|
||||
|
||||
| Характеристика | v1 | v2 |
|
||||
|----------------|----|----|
|
||||
| **Endpoints** | Все | Все (идентичные) |
|
||||
| **LDAP Sync** | Синхронный (Mixin) | Асинхронный (Signals) |
|
||||
| **Response Time** | ~200-300ms | ~30-50ms |
|
||||
| **Блокировка** | Да | Нет |
|
||||
| **Retry логика** | Ручная | Celery автоматически |
|
||||
| **Статус** | ✅ Работает | ✅ Работает |
|
||||
|
||||
**Оба API полностью функциональны!**
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Быстрый запуск
|
||||
|
||||
### 1. Настроить CORS (если еще не сделано)
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
.venv/Scripts/pip install django-cors-headers
|
||||
```
|
||||
|
||||
Добавить в `settings.py`:
|
||||
```python
|
||||
INSTALLED_APPS = ["corsheaders", ...]
|
||||
MIDDLEWARE = ["corsheaders.middleware.CorsMiddleware", ...]
|
||||
CORS_ALLOWED_ORIGINS = ["http://localhost:5173"]
|
||||
```
|
||||
|
||||
**Подробнее**: [Django CORS Setup](../../docs/guides/DJANGO_CORS_SETUP.md)
|
||||
|
||||
### 2. Запустить Backend
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
.venv/Scripts/python manage.py runserver 8000
|
||||
```
|
||||
|
||||
### 3. Запустить Frontend
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
npm install # первый раз
|
||||
npm run dev
|
||||
```
|
||||
|
||||
**Открыть**: http://localhost:5173
|
||||
|
||||
---
|
||||
|
||||
## ✅ Проверка работы
|
||||
|
||||
### Test 1: API v2 доступен
|
||||
|
||||
```bash
|
||||
curl http://localhost:8000/api/v2/employees/
|
||||
```
|
||||
|
||||
Должен вернуть JSON.
|
||||
|
||||
### Test 2: v1 всё ещё работает
|
||||
|
||||
```bash
|
||||
curl http://localhost:8000/api/v1/employees/
|
||||
```
|
||||
|
||||
Оба работают параллельно!
|
||||
|
||||
### Test 3: Frontend
|
||||
|
||||
Открыть http://localhost:5173 → должна загрузиться страница сотрудников.
|
||||
|
||||
---
|
||||
|
||||
## 📊 Статус проекта
|
||||
|
||||
| Модуль | Backend v2 | Frontend | Status |
|
||||
|--------|------------|----------|--------|
|
||||
| Auth | ✅ | ✅ | **Готово** |
|
||||
| Employees | ✅ | ✅ List | В разработке |
|
||||
| Departments | ✅ | ⏳ | Backend готов |
|
||||
| Documents | ✅ | ⏳ | Backend готов |
|
||||
| Requests | ✅ | ⏳ | Backend готов |
|
||||
| Calendar | ✅ | ⏳ | Backend готов |
|
||||
| Chats | ✅ | ⏳ | Backend готов |
|
||||
| Feed | ✅ | ⏳ | Backend готов |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Следующие задачи
|
||||
|
||||
### Фаза 1: Основной функционал
|
||||
- [ ] Добавить endpoint `/api/v2/employees/me/` (текущий пользователь)
|
||||
- [ ] Создать CRUD страницы для documents
|
||||
- [ ] Создать CRUD страницы для requests
|
||||
- [ ] Добавить UI библиотеку (Ant Design)
|
||||
|
||||
### Фаза 2: Продвинутый функционал
|
||||
- [ ] Интегрировать WebSocket для чатов
|
||||
- [ ] Добавить real-time уведомления
|
||||
- [ ] Календарь с FullCalendar
|
||||
- [ ] File uploads
|
||||
|
||||
### Фаза 3: Полировка
|
||||
- [ ] Мобильная адаптация
|
||||
- [ ] Темная тема
|
||||
- [ ] Тесты (Frontend + Backend)
|
||||
- [ ] Production build
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Переключение между v1 и v2
|
||||
|
||||
В любой момент можно переключиться:
|
||||
|
||||
**На v2 (по умолчанию):**
|
||||
```bash
|
||||
# frontend/.env
|
||||
VITE_API_URL=http://localhost:8000/api/v2
|
||||
```
|
||||
|
||||
**На v1:**
|
||||
```bash
|
||||
# frontend/.env
|
||||
VITE_API_URL=http://localhost:8000/api/v1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 Документация
|
||||
|
||||
- [API v2 Migration Guide](../../docs/guides/API_V2_MIGRATION.md)
|
||||
- [Frontend Quickstart](QUICKSTART.md)
|
||||
- [Django CORS Setup](../../docs/guides/DJANGO_CORS_SETUP.md)
|
||||
- [API v2 README](../../backend/api/v2/README.md)
|
||||
|
||||
---
|
||||
|
||||
**Всё готово для разработки на API v2!** 🚀
|
||||
|
||||
Frontend настроен, Backend готов, осталось только добавить CORS и можно работать!
|
||||
@ -1,410 +0,0 @@
|
||||
# Frontend Integration Guide
|
||||
|
||||
## Обзор
|
||||
|
||||
Фронтенд corp.front полностью интегрирован с Django Backend через REST API. Используется Next.js 16 с TypeScript и Tailwind CSS.
|
||||
|
||||
## Структура проекта
|
||||
|
||||
```
|
||||
corp.front/
|
||||
├── src/
|
||||
│ ├── app/ # Next.js страницы
|
||||
│ │ ├── page.tsx # Главная (Лента) ✅ Интегрирован
|
||||
│ │ ├── employees/ # Сотрудники ✅ Интегрирован
|
||||
│ │ ├── departments/ # Отделы
|
||||
│ │ ├── documents/ # Документы
|
||||
│ │ ├── messages/ # Сообщения
|
||||
│ │ ├── requests/ # Заявки
|
||||
│ │ ├── finances/ # Финансы
|
||||
│ │ └── login/ # Вход
|
||||
│ ├── lib/
|
||||
│ │ └── api.ts # API клиент ✅
|
||||
│ ├── types/
|
||||
│ │ └── api.ts # TypeScript типы ✅
|
||||
│ └── hooks/
|
||||
│ └── useApi.ts # React хуки для API ✅
|
||||
```
|
||||
|
||||
## API клиент
|
||||
|
||||
### Базовая конфигурация
|
||||
|
||||
API клиент автоматически:
|
||||
- Добавляет JWT токен к запросам
|
||||
- Обрабатывает ошибки авторизации
|
||||
- Сохраняет токены в localStorage
|
||||
- Перенаправляет на /login при 401
|
||||
|
||||
### Использование
|
||||
|
||||
```typescript
|
||||
import apiClient from '@/lib/api';
|
||||
|
||||
// Авторизация
|
||||
const { access, refresh, user } = await apiClient.login({
|
||||
username: 'user@example.com',
|
||||
password: 'password'
|
||||
});
|
||||
|
||||
// Получение данных
|
||||
const employees = await apiClient.getEmployees();
|
||||
const posts = await apiClient.getPosts();
|
||||
```
|
||||
|
||||
## React хуки
|
||||
|
||||
Используйте готовые хуки из `@/hooks/useApi`:
|
||||
|
||||
```tsx
|
||||
import { useCurrentUser, useEmployees, usePosts } from '@/hooks/useApi';
|
||||
|
||||
function MyComponent() {
|
||||
const { user, loading, error } = useCurrentUser();
|
||||
const { employees } = useEmployees({ search: 'John' });
|
||||
const { posts } = usePosts({ page: 1 });
|
||||
|
||||
if (loading) return <div>Загрузка...</div>;
|
||||
if (error) return <div>Ошибка: {error.message}</div>;
|
||||
|
||||
return <div>{/* UI */}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
### Доступные хуки
|
||||
|
||||
- `useCurrentUser()` - текущий пользователь
|
||||
- `useEmployees(params?)` - список сотрудников
|
||||
- `useDepartments()` - список отделов
|
||||
- `usePosts(params?)` - посты ленты
|
||||
- `useDocuments(params?)` - документы
|
||||
- `useRequests(params?)` - заявки
|
||||
- `useChats()` - чаты
|
||||
- `useNotifications()` - уведомления
|
||||
|
||||
## TypeScript типы
|
||||
|
||||
Все типы данных определены в `@/types/api.ts`:
|
||||
|
||||
```typescript
|
||||
import type { User, Post, Department, Document } from '@/types/api';
|
||||
```
|
||||
|
||||
## Интегрированные страницы
|
||||
|
||||
### ✅ Главная страница (Лента)
|
||||
|
||||
**Файл**: `src/app/page.tsx`
|
||||
|
||||
**Функции**:
|
||||
- Загрузка постов из API
|
||||
- Отображение автора, времени, контента
|
||||
- Лайки и комментарии
|
||||
- Теги постов
|
||||
|
||||
**Используемые хуки**:
|
||||
- `usePosts()` - получение постов
|
||||
- `useCurrentUser()` - текущий пользователь
|
||||
|
||||
### ✅ Сотрудники
|
||||
|
||||
**Файл**: `src/app/employees/page.tsx`
|
||||
|
||||
**Функции**:
|
||||
- Список всех сотрудников
|
||||
- Поиск по имени
|
||||
- Статус (онлайн/оффлайн)
|
||||
- Должность и отдел
|
||||
|
||||
**Используемые хуки**:
|
||||
- `useEmployees({ search })` - поиск сотрудников
|
||||
- `useCurrentUser()` - текущий пользователь
|
||||
|
||||
## Переменные окружения
|
||||
|
||||
### Для локальной разработки
|
||||
|
||||
`.env.local`:
|
||||
```env
|
||||
NEXT_PUBLIC_API_URL=http://localhost:8000
|
||||
NEXT_PUBLIC_WS_URL=ws://localhost:8000
|
||||
```
|
||||
|
||||
### Для Docker
|
||||
|
||||
`.env.local` (автоматически используется в docker-compose):
|
||||
```env
|
||||
NEXT_PUBLIC_API_URL=http://web:8000
|
||||
NEXT_PUBLIC_WS_URL=ws://web:8000
|
||||
```
|
||||
|
||||
## Запуск
|
||||
|
||||
### Локальная разработка
|
||||
|
||||
```bash
|
||||
cd corp.front
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Доступно на: http://localhost:3000
|
||||
|
||||
### Docker
|
||||
|
||||
```bash
|
||||
# Из корня проекта
|
||||
docker-compose up -d frontend
|
||||
```
|
||||
|
||||
Доступно через nginx: http://localhost
|
||||
|
||||
## Интеграция новых страниц
|
||||
|
||||
### Шаг 1: Создать страницу
|
||||
|
||||
```tsx
|
||||
// src/app/mypage/page.tsx
|
||||
"use client";
|
||||
|
||||
import { useMyData } from '@/hooks/useApi';
|
||||
|
||||
export default function MyPage() {
|
||||
const { data, loading, error } = useMyData();
|
||||
|
||||
if (loading) return <div>Загрузка...</div>;
|
||||
if (error) return <div>Ошибка: {error.message}</div>;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{data.map(item => (
|
||||
<div key={item.id}>{item.name}</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Шаг 2: Добавить метод в API клиент
|
||||
|
||||
```typescript
|
||||
// src/lib/api.ts
|
||||
class ApiClient {
|
||||
// ... existing methods
|
||||
|
||||
async getMyData() {
|
||||
return this.request('/mydata/');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Шаг 3: Создать хук (опционально)
|
||||
|
||||
```typescript
|
||||
// src/hooks/useApi.ts
|
||||
export function useMyData() {
|
||||
const [data, setData] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchData() {
|
||||
try {
|
||||
const result = await apiClient.getMyData();
|
||||
setData(result);
|
||||
} catch (err) {
|
||||
setError(err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
return { data, loading, error };
|
||||
}
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
Полный список доступных эндпоинтов:
|
||||
|
||||
### Auth
|
||||
- `POST /api/auth/token/` - Login
|
||||
- `POST /api/auth/token/refresh/` - Refresh token
|
||||
- `POST /api/v1/auth/register/` - Регистрация
|
||||
|
||||
### Employees
|
||||
- `GET /api/v1/employees/` - Список сотрудников
|
||||
- `GET /api/v1/employees/{id}/` - Детали сотрудника
|
||||
- `GET /api/v1/employees/me/` - Текущий пользователь
|
||||
|
||||
### Departments
|
||||
- `GET /api/v1/departments/` - Список отделов
|
||||
- `GET /api/v1/departments/{id}/` - Детали отдела
|
||||
|
||||
### Feed
|
||||
- `GET /api/v1/posts/` - Посты ленты
|
||||
- `POST /api/v1/posts/` - Создать пост
|
||||
- `POST /api/v1/posts/{id}/like/` - Лайк поста
|
||||
- `GET /api/v1/comments/` - Комментарии
|
||||
- `POST /api/v1/comments/` - Создать комментарий
|
||||
|
||||
### Documents
|
||||
- `GET /api/v1/documents/` - Документы
|
||||
- `GET /api/v1/documents/{id}/` - Детали документа
|
||||
|
||||
### Requests
|
||||
- `GET /api/v1/requests/` - Заявки
|
||||
- `POST /api/v1/requests/` - Создать заявку
|
||||
- `GET /api/v1/requests/{id}/` - Детали заявки
|
||||
|
||||
### Communications
|
||||
- `GET /api/v1/communications/chats/` - Чаты
|
||||
- `GET /api/v1/communications/messages/` - Сообщения
|
||||
- `POST /api/v1/communications/messages/` - Отправить сообщение
|
||||
|
||||
### Calendar
|
||||
- `GET /api/v1/calendar/events/` - События календаря
|
||||
- `GET /api/v1/calendar/calendars/` - Календари
|
||||
|
||||
### Notifications
|
||||
- `GET /api/v1/notifications/` - Уведомления
|
||||
- `POST /api/v1/notifications/{id}/mark_read/` - Отметить прочитанным
|
||||
- `POST /api/v1/notifications/mark_all_read/` - Отметить все прочитанными
|
||||
|
||||
## Аутентификация
|
||||
|
||||
### Вход
|
||||
|
||||
```typescript
|
||||
import apiClient from '@/lib/api';
|
||||
|
||||
async function handleLogin(username: string, password: string) {
|
||||
try {
|
||||
const { access, refresh, user } = await apiClient.login({
|
||||
username,
|
||||
password
|
||||
});
|
||||
|
||||
// Токен автоматически сохранён
|
||||
// Перенаправить пользователя
|
||||
router.push('/');
|
||||
} catch (error) {
|
||||
console.error('Login failed:', error);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Выход
|
||||
|
||||
```typescript
|
||||
import apiClient from '@/lib/api';
|
||||
|
||||
function handleLogout() {
|
||||
apiClient.clearToken();
|
||||
router.push('/login');
|
||||
}
|
||||
```
|
||||
|
||||
### Защищённые страницы
|
||||
|
||||
```tsx
|
||||
"use client";
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import apiClient from '@/lib/api';
|
||||
|
||||
export default function ProtectedPage() {
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
const token = apiClient.getToken();
|
||||
if (!token) {
|
||||
router.push('/login');
|
||||
}
|
||||
}, []);
|
||||
|
||||
return <div>Protected content</div>;
|
||||
}
|
||||
```
|
||||
|
||||
## Обработка ошибок
|
||||
|
||||
API клиент автоматически обрабатывает:
|
||||
|
||||
- **401 Unauthorized**: Очищает токены и перенаправляет на `/login`
|
||||
- **Network errors**: Выбрасывает исключение с сообщением
|
||||
|
||||
Пример обработки в компонентах:
|
||||
|
||||
```tsx
|
||||
const { data, loading, error } = useMyData();
|
||||
|
||||
if (loading) return <LoadingSpinner />;
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="error-message">
|
||||
<h3>Ошибка загрузки данных</h3>
|
||||
<p>{error.message}</p>
|
||||
<button onClick={() => refetch()}>Попробовать снова</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## WebSocket (для будущей реализации)
|
||||
|
||||
Для real-time обновлений можно добавить WebSocket соединение:
|
||||
|
||||
```typescript
|
||||
const WS_URL = process.env.NEXT_PUBLIC_WS_URL || 'ws://localhost:8000';
|
||||
|
||||
const socket = new WebSocket(`${WS_URL}/ws/notifications/`);
|
||||
|
||||
socket.onmessage = (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
// Обработать уведомление
|
||||
};
|
||||
```
|
||||
|
||||
## Рекомендации
|
||||
|
||||
1. **Всегда используйте хуки** для получения данных из API
|
||||
2. **Обрабатывайте loading и error** состояния в UI
|
||||
3. **Используйte TypeScript типы** для type-safety
|
||||
4. **Не сохраняйте чувствительные данные** в localStorage кроме токенов
|
||||
5. **Валидируйте данные** перед отправкой на сервер
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
###CORS ошибки
|
||||
|
||||
Убедитесь, что backend настроен с правильными CORS заголовками:
|
||||
|
||||
```python
|
||||
# backend/eusrr_backend/settings.py
|
||||
CORS_ALLOWED_ORIGINS = [
|
||||
"http://localhost:3000",
|
||||
"http://localhost",
|
||||
]
|
||||
```
|
||||
|
||||
### 401 Unauthorized
|
||||
|
||||
- Проверьте, что токен действителен
|
||||
- Убедитесь, что backend запущен
|
||||
- Проверьте переменные окружения
|
||||
|
||||
### Ошибка сети
|
||||
|
||||
- Проверьте, что backend доступен
|
||||
- Проверьте URL в `NEXT_PUBLIC_API_URL`
|
||||
- Проверьте docker network, если используете docker-compose
|
||||
|
||||
---
|
||||
|
||||
**Дата создания**: 19 февраля 2026
|
||||
**Версия**: 1.0
|
||||
@ -17,6 +17,18 @@ cd backend
|
||||
.venv/Scripts/pip install django-cors-headers
|
||||
```
|
||||
|
||||
### 1.5. Проверить что Daphne установлен
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
.venv/Scripts/pip show daphne
|
||||
```
|
||||
|
||||
Если Daphne не установлен:
|
||||
```bash
|
||||
.venv/Scripts/pip install daphne
|
||||
```
|
||||
|
||||
Добавить в `backend/eusrr_backend/settings.py`:
|
||||
|
||||
```python
|
||||
@ -52,7 +64,15 @@ npm install
|
||||
|
||||
### 3. Запустить оба сервера
|
||||
|
||||
**Terminal 1 - Django:**
|
||||
**⚠️ ВАЖНО: Для WebSocket используйте Daphne вместо стандартного Django сервера!**
|
||||
|
||||
**Terminal 1 - Django (с WebSocket на Daphne):**
|
||||
```bash
|
||||
cd backend
|
||||
.venv/Scripts/daphne -b 0.0.0.0 -p 9000 eusrr_backend.asgi:application
|
||||
```
|
||||
|
||||
Или используйте `runserver` для HTTP (без WebSocket):
|
||||
```bash
|
||||
cd backend
|
||||
.venv/Scripts/python manage.py runserver 8000
|
||||
@ -68,6 +88,16 @@ npm run dev
|
||||
|
||||
Откройте в браузере: **http://localhost:5173**
|
||||
|
||||
## 🔍 Проверка WebSocket подключения
|
||||
|
||||
1. Откройте **DevTools** (F12) → **Console**
|
||||
2. Ищите сообщение `🔄 Подключение к WebSocket: ws://localhost:9000/ws/...`
|
||||
3. Если ошибка `❌ WebSocket error: {}`, проверьте:
|
||||
- ✅ Backend запущен через Daphne (не через runserver)
|
||||
- ✅ PORT 9000 доступен (не занят другой программой)
|
||||
- ✅ JWT токен сохранен в localStorage
|
||||
- ✅ В файле `.env.local` стоит `NEXT_PUBLIC_WS_URL=ws://localhost:9000`
|
||||
|
||||
## 🔐 Тестовая авторизация
|
||||
|
||||
Используйте существующего пользователя из вашей БД:
|
||||
@ -77,6 +107,8 @@ Email: ваш_email@example.com
|
||||
Password: ваш_пароль
|
||||
```
|
||||
|
||||
После входа токен автоматически сохранится в `localStorage` и будет использован для WebSocket аутентификации.
|
||||
|
||||
## 📁 Структура страниц
|
||||
|
||||
Сейчас доступно:
|
||||
@ -124,10 +156,40 @@ touch src/pages/documents/index.ts
|
||||
- Проверьте что пользователь существует в БД
|
||||
- Проверьте endpoint: `http://localhost:8000/api/auth/token/`
|
||||
|
||||
### WebSocket error: {}
|
||||
**Это самая частая ошибка!** Решение:
|
||||
|
||||
1. **Проверьте что backend запущен через Daphne:**
|
||||
```bash
|
||||
cd backend
|
||||
.venv/Scripts/daphne -b 0.0.0.0 -p 9000 eusrr_backend.asgi:application
|
||||
```
|
||||
|
||||
2. **НЕ используйте `python manage.py runserver`** - это не поддерживает WebSocket!
|
||||
|
||||
3. **Убедитесь что .env.local имеет правильный URL:**
|
||||
```dotenv
|
||||
NEXT_PUBLIC_WS_URL=ws://localhost:9000
|
||||
NEXT_PUBLIC_BACKEND_URL=http://localhost:9000
|
||||
```
|
||||
|
||||
4. **Проверьте в DevTools → Network → WS:**
|
||||
- Введите фильтр `ws` чтобы найти WebSocket подключение
|
||||
- Если соединение **101 Switching Protocols** - всё работает ✅
|
||||
- Если ошибка **Failed** - backend не запущен через Daphne ❌
|
||||
|
||||
5. **Проверьте порт 9000:**
|
||||
```bash
|
||||
# Windows - проверить какой процесс занимает порт
|
||||
netstat -ano | findstr :9000
|
||||
|
||||
# Если занято - убейте процесс или используйте другой порт
|
||||
```
|
||||
|
||||
### Страница не загружается
|
||||
- Убедитесь что оба сервера запущены
|
||||
- Проверьте `.env` файл в frontend
|
||||
- Откройте DevTools → Network
|
||||
- Проверьте `.env.local` файл в frontend
|
||||
- Откройте DevTools → Network и посмотрите статусы запросов
|
||||
|
||||
## 📚 Документация
|
||||
|
||||
|
||||
@ -23,18 +23,18 @@ interface UseWebSocketOptions {
|
||||
maxReconnectAttempts?: number;
|
||||
}
|
||||
|
||||
export function useWebSocket({
|
||||
chatId,
|
||||
export function useWebSocket({
|
||||
chatId,
|
||||
autoConnect = true,
|
||||
reconnectInterval = 3000,
|
||||
maxReconnectAttempts = 5
|
||||
maxReconnectAttempts = 5
|
||||
}: UseWebSocketOptions) {
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
const [reconnectAttempts, setReconnectAttempts] = useState(0);
|
||||
const wsRef = useRef<WebSocket | null>(null);
|
||||
const reconnectTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const isConnectingRef = useRef(false);
|
||||
|
||||
|
||||
const handlers = useRef<WebSocketHandlers>({
|
||||
onMessage: () => {},
|
||||
onTyping: () => {},
|
||||
@ -51,15 +51,22 @@ export function useWebSocket({
|
||||
isConnectingRef.current = true;
|
||||
|
||||
try {
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const host = process.env.NEXT_PUBLIC_WS_URL || 'localhost:9000';
|
||||
|
||||
// Получаем JWT токен для аутентификации WebSocket
|
||||
// Простую логику построения URL
|
||||
let wsUrl = process.env.NEXT_PUBLIC_WS_URL || 'ws://localhost:9000/ws/';
|
||||
|
||||
// // Если нет протокола - добавляем
|
||||
// if (!wsUrl.startsWith('ws://') && !wsUrl.startsWith('wss://')) {
|
||||
// const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
// wsUrl = `${protocol}//${wsUrl}`;
|
||||
// }
|
||||
|
||||
// Добавляем токен
|
||||
const token = localStorage.getItem('access_token');
|
||||
const wsUrl = token
|
||||
? `${protocol}//${host}/ws/?token=${token}`
|
||||
: `${protocol}//${host}/ws/`;
|
||||
|
||||
if (token) {
|
||||
wsUrl += `${wsUrl.includes('?') ? '&' : '?'}token=${token}`;
|
||||
}
|
||||
|
||||
console.log('🔄 WebSocket URL:', wsUrl);
|
||||
const ws = new WebSocket(wsUrl);
|
||||
wsRef.current = ws;
|
||||
|
||||
@ -67,7 +74,7 @@ export function useWebSocket({
|
||||
setIsConnected(true);
|
||||
setReconnectAttempts(0);
|
||||
isConnectingRef.current = false;
|
||||
|
||||
|
||||
// Открываем чат на сервере
|
||||
ws.send(JSON.stringify({
|
||||
action: 'open_chat',
|
||||
@ -136,7 +143,7 @@ export function useWebSocket({
|
||||
|
||||
if (wsRef.current) {
|
||||
const ws = wsRef.current;
|
||||
|
||||
|
||||
if (ws.readyState === WebSocket.OPEN && chatId) {
|
||||
ws.send(JSON.stringify({
|
||||
action: 'close_chat',
|
||||
@ -155,8 +162,8 @@ export function useWebSocket({
|
||||
|
||||
const sendTyping = useCallback(() => {
|
||||
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
||||
wsRef.current.send(JSON.stringify({
|
||||
action: 'typing'
|
||||
wsRef.current.send(JSON.stringify({
|
||||
action: 'typing'
|
||||
}));
|
||||
}
|
||||
}, []);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user