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.


Запуск и деплой

Деплой на сервер

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 (важно!)

Локальная разработка

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

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> 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=<id>)
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=<JWT>

Клиент → Сервер

// Отправить сообщение
{ "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": "новый текст" } }

Сервер → Клиент

{ "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)

Полезные команды

# Логи 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
Description
JaniChat — corporate messenger
Readme 357 KiB
Languages
TypeScript 97.1%
JavaScript 1.3%
CSS 0.7%
HTML 0.4%
Dockerfile 0.4%
Other 0.1%