Files
janichat/README.md
2026-05-22 12:50:01 +03:00

342 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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>`
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>`
### Клиент → Сервер
```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
```