docs: add project README with API, architecture, deploy instructions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ai
2026-05-22 12:50:01 +03:00
parent 87c6d7f82f
commit c84f4a34e1

341
README.md Normal file
View File

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