diff --git a/README.md b/README.md new file mode 100644 index 0000000..3922b2f --- /dev/null +++ b/README.md @@ -0,0 +1,341 @@ +# JaniChat + +Корпоративный мессенджер в браузере. Telegram-подобный интерфейс, PWA, push-уведомления. Пользователей создаёт администратор — свободной регистрации нет. + +**Домены:** +- `https://janichat.ru` — фронтенд (React PWA) +- `https://api.janichat.ru` — бэкенд API + WebSocket + +--- + +## Стек + +| Слой | Технологии | +|------|-----------| +| Frontend | React 18, TypeScript, Vite, Tailwind CSS, Zustand | +| Backend | Fastify, TypeScript, Node.js 20 | +| БД | PostgreSQL (контейнер `hotelsync-postgres`) | +| Realtime | WebSocket (`@fastify/websocket`) | +| Push | Web Push API (VAPID) | +| Деплой | Docker Compose, nginx (hotelsync-nginx) | + +--- + +## Архитектура + +``` +janichat.ru api.janichat.ru + │ │ +hotelsync-nginx (nginx)────┤ + │ │ +janichat-frontend janichat-api + (nginx:80) (fastify:3000) + │ + hotelsync-postgres + (база: janichat) +``` + +Оба контейнера находятся в сети `hotelsync_hotelsync-net` — общей сети с основным проектом HotelSync. + +--- + +## Запуск и деплой + +### Деплой на сервер + +```bash +ssh root@92.63.177.212 "cd /opt/janichat && bash deploy.sh" +``` + +Скрипт `/opt/janichat/deploy.sh`: +1. `git pull origin main` из `/opt/janichat/app` +2. `docker compose build --no-cache` +3. `docker compose up -d` +4. `docker exec hotelsync-nginx nginx -s reload` — обновляет IP контейнеров в nginx (важно!) + +### Локальная разработка + +```bash +# Backend +cd backend +npm install +cp .env.example .env # заполнить DATABASE_URL, JWT_SECRET, VAPID ключи +npm run dev + +# Frontend +cd frontend +npm install +npm run dev +``` + +### Переменные окружения (сервер: `/opt/janichat/.env`) + +```env +DATABASE_URL=postgresql://hotelsync:PASSWORD@postgres:5432/janichat +JWT_SECRET=... +VAPID_PUBLIC_KEY=... +VAPID_PRIVATE_KEY=... +VAPID_EMAIL=mailto@example.com +ALLOWED_ORIGINS=https://janichat.ru +``` + +Frontend build args (Dockerfile): +``` +VITE_API_URL=https://api.janichat.ru +VITE_WS_URL=wss://api.janichat.ru +``` + +--- + +## База данных + +База `janichat` в контейнере `hotelsync-postgres`. Схема создаётся автоматически при старте бэкенда (`initDB()`). + +### Таблицы + +| Таблица | Описание | +|---------|----------| +| `users` | Пользователи: login, display_name, phone, position, avatar, is_admin | +| `chats` | Чаты: private / group / channel | +| `chat_members` | Участники чатов: роль, права (can_send_messages, can_add_members…) | +| `messages` | Сообщения: text / image / file / system | +| `attachments` | Файлы, прикреплённые к сообщениям | +| `push_subscriptions` | Подписки Web Push | + +### Роли участников + +| Роль | Права | +|------|-------| +| `owner` | Всё, включая удаление чата | +| `admin` | Управление участниками, смена ролей | +| `member` | Чтение и отправка (если разрешено) | + +### Миграции + +Выполняются автоматически при старте (`initDB`). Новые колонки добавляются через `ALTER TABLE ... ADD COLUMN IF NOT EXISTS`. + +--- + +## API + +Базовый URL: `https://api.janichat.ru` + +Аутентификация: `Authorization: Bearer ` +JWT выдаётся при логине, действителен 30 дней. + +### Auth + +| Метод | Путь | Описание | +|-------|------|----------| +| POST | `/api/auth/login` | Вход (по phone или username + password) | +| GET | `/api/auth/me` | Текущий пользователь | +| PUT | `/api/auth/me` | Обновить профиль (displayName, bio, phone, position, password) | +| PUT | `/api/auth/avatar` | Загрузить аватар (multipart) | +| DELETE | `/api/auth/avatar` | Удалить аватар | + +### Users + +| Метод | Путь | Описание | +|-------|------|----------| +| GET | `/api/users` | Все пользователи (кроме себя) | +| GET | `/api/users/search?q=` | Поиск по имени, логину, телефону | +| GET | `/api/users/:id` | Профиль пользователя | + +### Chats + +| Метод | Путь | Описание | +|-------|------|----------| +| GET | `/api/chats` | Мои чаты | +| POST | `/api/chats/private/:userId` | Создать/открыть личный чат | +| POST | `/api/chats` | Создать группу или канал | +| GET | `/api/chats/:id` | Чат с участниками | +| PUT | `/api/chats/:id` | Обновить название/описание | +| DELETE | `/api/chats/:id` | Удалить (owner или admin системы) | +| PUT | `/api/chats/:id/avatar` | Загрузить аватар чата | +| DELETE | `/api/chats/:id/avatar` | Удалить аватар чата | +| POST | `/api/chats/:id/members` | Добавить участника | +| DELETE | `/api/chats/:id/members/:memberId` | Удалить участника | +| PUT | `/api/chats/:id/members/:memberId` | Изменить роль / права | +| DELETE | `/api/chats/:id/leave` | Покинуть чат | + +### Messages + +| Метод | Путь | Описание | +|-------|------|----------| +| GET | `/api/messages/chat/:chatId` | История (50 последних, курсор: `?before=`) | +| POST | `/api/messages/chat/:chatId` | Отправить текстовое сообщение (HTTP fallback) | +| POST | `/api/messages/chat/:chatId/upload` | Загрузить файл/изображение | + +### Push + +| Метод | Путь | Описание | +|-------|------|----------| +| GET | `/api/push/key` | Публичный VAPID ключ | +| POST | `/api/push/subscribe` | Подписаться на push | +| DELETE | `/api/push/unsubscribe` | Отписаться | + +### Admin (только is_admin) + +| Метод | Путь | Описание | +|-------|------|----------| +| GET | `/api/admin/users` | Все пользователи | +| POST | `/api/admin/users` | Создать пользователя | +| PUT | `/api/admin/users/:id` | Обновить пользователя (displayName, phone, position, isAdmin, isActive, password) | +| DELETE | `/api/admin/users/:id` | Удалить пользователя | +| GET | `/api/admin/chats` | Все чаты системы | +| DELETE | `/api/admin/chats/:id` | Удалить чат | +| GET | `/api/admin/stats` | Статистика (users, chats, messages) | + +--- + +## WebSocket + +Подключение: `wss://api.janichat.ru/ws?token=` + +### Клиент → Сервер + +```jsonc +// Отправить сообщение +{ "type": "send_message", "payload": { "chatId": "uuid", "content": "текст", "type": "text", "replyToId": null } } + +// Индикатор набора +{ "type": "typing", "payload": { "chatId": "uuid", "typing": true } } + +// Прочитать сообщения +{ "type": "read_messages", "payload": { "chatId": "uuid" } } + +// Удалить сообщение +{ "type": "delete_message", "payload": { "messageId": "uuid" } } + +// Редактировать сообщение +{ "type": "edit_message", "payload": { "messageId": "uuid", "content": "новый текст" } } +``` + +### Сервер → Клиент + +```jsonc +{ "type": "new_message", "payload": { /* Message объект */ } } +{ "type": "message_edited", "payload": { /* Message объект */ } } +{ "type": "message_deleted", "payload": { "messageId": "uuid", "chatId": "uuid" } } +{ "type": "user_online", "payload": { "userId": "uuid", "online": true } } +{ "type": "typing", "payload": { "chatId": "uuid", "userId": "uuid", "typing": true } } +{ "type": "messages_read", "payload": { "chatId": "uuid", "userId": "uuid" } } +``` + +--- + +## Фронтенд + +### Структура + +``` +src/ +├── api/ +│ ├── client.ts # Axios с JWT из localStorage +│ └── ws.ts # WsClient (WebSocket + автореконнект) +├── components/ +│ ├── Avatar.tsx # Аватар (фото или инициалы) +│ ├── ChatHeader.tsx # Шапка чата +│ ├── ChatInfoPanel.tsx # Боковая панель: участники, аватар чата +│ ├── ChatListItem.tsx # Элемент списка чатов +│ ├── MessageInput.tsx # Поле ввода (WS + HTTP fallback) +│ ├── MessageItem.tsx # Пузырь сообщения +│ ├── MessageList.tsx # Список сообщений + пагинация +│ ├── NewChatModal.tsx # Создание чата/группы/канала +│ ├── ProfileModal.tsx # Профиль пользователя + выход +│ └── admin/ +│ └── AdminPanel.tsx # Панель администратора +├── pages/ +│ ├── Login.tsx # Страница входа (по телефону) +│ └── MainLayout.tsx # Основной layout: сайдбар + чат +├── store/ +│ └── index.ts # Zustand store +├── hooks/ +│ └── usePushNotifications.ts +├── types.ts # TypeScript типы +└── App.tsx # Роутинг, инициализация WS, восстановление сессии +``` + +### Управление состоянием (Zustand) + +Главный store (`store/index.ts`) хранит: +- `user` — текущий пользователь (инициализируется из localStorage) +- `chats` — список чатов +- `activeChat`, `fullChat` — открытый чат +- `messages` — сообщения по chatId +- `onlineUsers` — множество userId онлайн-пользователей +- `connected` — статус WebSocket + +### Сессия + +Токен и данные пользователя хранятся в `localStorage` (`jc_token`, `jc_user`). Zustand инициализируется сразу из `localStorage` — при перезагрузке страницы сессия сохраняется без запроса к серверу. + +--- + +## PWA + +Приложение устанавливается на мобильные устройства через "Добавить на главный экран". + +- `public/manifest.json` — название, иконки, цвет темы +- `public/sw.js` — Service Worker: кэш, обработка push-уведомлений, открытие чата по клику на уведомление +- Иконки: `icon-192.png`, `icon-512.png`, `apple-touch-icon.png` (алмаз на синем фоне) + +--- + +## Права доступа + +### Типы чатов + +| Тип | Кто пишет | +|-----|-----------| +| `private` | Оба участника | +| `group` | Все участники (можно ограничить через `can_send_messages`) | +| `channel` | Только owner и admin | + +### Создание пользователей + +Только администратор (`is_admin = true`) через панель администратора. Свободная регистрация отсутствует. + +--- + +## Учётные записи по умолчанию + +При первом запуске (пустая БД) создаётся: + +| Логин | Пароль | Роль | +|-------|--------|------| +| `admin` | `admin123` | Администратор | + +> **Смените пароль после первого входа!** + +--- + +## Сервер + +| Параметр | Значение | +|----------|----------| +| IP | 92.63.177.212 | +| ОС | Ubuntu 24.04 LTS | +| SSH | `ssh root@92.63.177.212` (ключ `~/.ssh/id_ed25519`) | +| Проект | `/opt/janichat/` | +| Git-репо (сервер) | `/opt/janichat/app/` | +| Загрузки | `/uploads/` (внутри контейнера janichat-api) | + +### Полезные команды + +```bash +# Логи API +docker logs janichat-api --tail=50 -f + +# Логи фронтенда +docker logs janichat-frontend --tail=20 + +# Перезапустить только API +docker restart janichat-api + +# Войти в контейнер API +docker exec -it janichat-api sh + +# Перезагрузить nginx (после смены IP контейнеров) +docker exec hotelsync-nginx nginx -s reload +```