docs: add project README with API, architecture, deploy instructions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
341
README.md
Normal file
341
README.md
Normal 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
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user