# 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 ```