1237 lines
39 KiB
Markdown
1237 lines
39 KiB
Markdown
# Chat API Documentation
|
||
|
||
## Обзор
|
||
|
||
Система чата является комплексным решением для обмена сообщениями в CRM системе, поддерживающим множественные провайдеры (WhatsApp, Telegram, Messenger и др.), персональные и групповые чаты, а также интеграцию с сущностями системы.
|
||
|
||
## Архитектура
|
||
|
||
### Компоненты системы
|
||
|
||
1. **Backend (NestJS)**
|
||
- REST API для управления чатами
|
||
- WebSocket для real-time сообщений
|
||
- Интеграция с внешними провайдерами
|
||
|
||
2. **Frontend (React/TypeScript)**
|
||
- Веб-интерфейс для управления чатами
|
||
- Модальные окна и встроенные компоненты
|
||
|
||
3. **Android приложение**
|
||
- Мобильный клиент для Android устройств
|
||
- Полная функциональность чата
|
||
|
||
4. **База данных**
|
||
- PostgreSQL с TypeORM
|
||
- Комплексная схема для чатов, сообщений, пользователей
|
||
|
||
## Backend API
|
||
|
||
### Детальная спецификация API
|
||
|
||
#### MultichatController (`/chat`)
|
||
Основной контроллер для общих операций с чатами.
|
||
|
||
```typescript
|
||
@ApiTags('multichat')
|
||
@Controller('chat')
|
||
@JwtAuthorized()
|
||
export class MultichatController {
|
||
// Получение количества непрочитанных сообщений
|
||
@Get('unseen-count')
|
||
async getUnseenCount(): Promise<number>
|
||
|
||
// Обновление статуса сообщений для всех чатов
|
||
@Put('status/:status')
|
||
async updateMessagesStatus()
|
||
}
|
||
```
|
||
|
||
##### GET /chat/unseen-count
|
||
**Описание:** Получить общее количество непрочитанных сообщений для текущего пользователя.
|
||
|
||
**Аутентификация:** JWT токен обязательна
|
||
|
||
**Параметры:** Нет
|
||
|
||
**Ответ:**
|
||
```json
|
||
{
|
||
"statusCode": 200,
|
||
"data": 5
|
||
}
|
||
```
|
||
|
||
##### PUT /chat/status/{status}
|
||
**Описание:** Обновить статус всех сообщений во всех чатах пользователя.
|
||
|
||
**Аутентификация:** JWT токен обязательна
|
||
|
||
**Параметры пути:**
|
||
- `status` (string): Новый статус (received/seen)
|
||
|
||
**Тело запроса:**
|
||
```json
|
||
{
|
||
"accountId": 1,
|
||
"user": {...},
|
||
"status": "seen"
|
||
}
|
||
```
|
||
|
||
**Ответ:**
|
||
```json
|
||
{
|
||
"statusCode": 200,
|
||
"message": "Messages status updated"
|
||
}
|
||
```
|
||
|
||
#### ChatController (`/chat/chats`)
|
||
Контроллер для операций с конкретными чатами.
|
||
|
||
**Создание чатов:**
|
||
- `POST /personal` - персональный чат
|
||
- `POST /group` - групповой чат
|
||
- `POST /external` - внешний чат
|
||
|
||
**Получение чатов:**
|
||
- `GET /` - список чатов с пагинацией
|
||
- `GET /find` - простой поиск
|
||
- `GET /find/full` - полный поиск с дополнительными данными
|
||
- `GET /find/full/personal` - поиск по имени пользователя
|
||
- `GET /find/full/by-message-content` - поиск по содержимому сообщений
|
||
- `GET /:chatId` - получение конкретного чата
|
||
|
||
**Управление чатами:**
|
||
- `PATCH /group/:chatId` - обновление группового чата
|
||
- `DELETE /:chatId` - удаление чата
|
||
- `PUT /:chatId/pin/:messageId` - закрепление сообщения
|
||
- `PUT /:chatId/unpin/:messageId` - открепление сообщения
|
||
- `PUT /:chatId/status/:status` - обновление статуса сообщений в чате
|
||
|
||
**Дополнительные операции:**
|
||
- `POST /:chatId/contact` - создание контакта и лида
|
||
|
||
#### ChatMessageController (`/chat/chats/:chatId/messages`)
|
||
Контроллер для работы с сообщениями в чате.
|
||
|
||
**Операции с сообщениями:**
|
||
- `POST /` - отправка сообщения
|
||
- `GET /` - получение списка сообщений
|
||
- `GET /:messageId` - получение конкретного сообщения
|
||
- `PUT /:messageId` - редактирование сообщения
|
||
- `DELETE /:messageId` - удаление сообщения
|
||
|
||
**Статусы сообщений:**
|
||
- `POST /status/:status` - обновление статуса нескольких сообщений
|
||
- `PUT /:messageId/status/:status` - обновление статуса одного сообщения
|
||
|
||
**Реакции:**
|
||
- `PUT /:messageId/react/:reaction` - добавление реакции
|
||
- `PUT /:messageId/unreact/:reactionId` - удаление реакции
|
||
|
||
#### ChatProviderController (`/chat/providers`)
|
||
Контроллер для работы с провайдерами чатов.
|
||
|
||
```typescript
|
||
@ApiTags('multichat/providers')
|
||
@Controller('chat/providers')
|
||
@JwtAuthorized()
|
||
export class ChatProviderController {
|
||
@Get()
|
||
async findMany(): Promise<ChatProviderDto[]>
|
||
}
|
||
```
|
||
|
||
### Модели данных
|
||
|
||
#### Chat (Чат)
|
||
```typescript
|
||
export class ChatDto {
|
||
id: number; // ID чата
|
||
providerId: number; // ID провайдера
|
||
createdBy: number; // ID создателя
|
||
externalId: string | null; // Внешний ID
|
||
type: ChatType; // Тип чата (PERSONAL/GROUP)
|
||
title: string | null; // Название
|
||
entityId: number | null; // ID связанной сущности
|
||
createdAt: string; // Дата создания
|
||
users: ChatUserDto[]; // Пользователи чата
|
||
pinnedMessages: ChatMessageDto[]; // Закрепленные сообщения
|
||
lastMessage: ChatMessageDto | null; // Последнее сообщение
|
||
unseenCount: number; // Количество непрочитанных
|
||
updatedAt: string; // Дата обновления
|
||
entityInfo: EntityInfoDto | null; // Информация о сущности
|
||
hasAccess?: boolean | null; // Доступ пользователя
|
||
}
|
||
```
|
||
|
||
#### ChatMessage (Сообщение)
|
||
```typescript
|
||
export class ChatMessageDto {
|
||
id: number; // ID сообщения
|
||
chatId: number; // ID чата
|
||
chatUserId: number; // ID пользователя чата
|
||
text: string | null; // Текст сообщения
|
||
replyToId: number | null; // ID сообщения-ответа
|
||
createdAt: string; // Дата создания
|
||
files: ChatMessageFileDto[]; // Прикрепленные файлы
|
||
reactions: ChatMessageReactionDto[]; // Реакции
|
||
status: ChatMessageStatus; // Статус для текущего пользователя
|
||
}
|
||
```
|
||
|
||
#### ChatUser (Пользователь чата)
|
||
```typescript
|
||
export class ChatUserDto {
|
||
id: number; // ID в чате
|
||
chatId: number; // ID чата
|
||
userId: number | null; // ID пользователя системы
|
||
role: ChatUserRole; // Роль в чате
|
||
name: string | null; // Имя
|
||
avatarUrl: string | null; // URL аватара
|
||
}
|
||
```
|
||
|
||
#### ChatProvider (Провайдер)
|
||
```typescript
|
||
export class ChatProviderDto {
|
||
id: number; // ID провайдера
|
||
type: ChatProviderType; // Тип провайдера
|
||
title: string; // Название
|
||
status: ChatProviderStatus; // Статус
|
||
transport: ChatProviderTransport; // Транспорт
|
||
unseenCount: number; // Непрочитанные сообщения
|
||
}
|
||
```
|
||
|
||
### Статусы и типы
|
||
|
||
#### ChatType
|
||
```typescript
|
||
export enum ChatType {
|
||
PERSONAL = 'personal', // Персональный чат между двумя пользователями
|
||
GROUP = 'group' // Групповой чат с множеством участников
|
||
}
|
||
```
|
||
|
||
#### ChatMessageStatus
|
||
```typescript
|
||
export enum ChatMessageStatus {
|
||
RECEIVED = 'received', // Сообщение получено
|
||
SEEN = 'seen' // Сообщение просмотрено
|
||
}
|
||
```
|
||
|
||
#### ChatProviderType
|
||
```typescript
|
||
export enum ChatProviderType {
|
||
Amwork = 'amwork', // Внутренняя система Amwork
|
||
Twilio = 'twilio', // Twilio WhatsApp
|
||
Facebook = 'facebook', // Facebook Messenger
|
||
Wazzup = 'wazzup' // Wazzup WhatsApp
|
||
}
|
||
```
|
||
|
||
#### ChatProviderTransport
|
||
```typescript
|
||
export enum ChatProviderTransport {
|
||
Amwork = 'amwork', // Внутренняя система
|
||
Whatsapp = 'whatsapp', // WhatsApp
|
||
Messenger = 'messenger', // Facebook Messenger
|
||
Telegram = 'telegram', // Telegram
|
||
Instagram = 'instagram', // Instagram
|
||
Vk = 'vk', // VKontakte
|
||
Avito = 'avito' // Avito
|
||
}
|
||
```
|
||
|
||
#### ChatProviderStatus
|
||
```typescript
|
||
export enum ChatProviderStatus {
|
||
Draft = 'draft', // Черновик настройки
|
||
Active = 'active', // Активен
|
||
Inactive = 'inactive', // Неактивен
|
||
Deleted = 'deleted' // Удален
|
||
}
|
||
```
|
||
|
||
#### ChatUserRole
|
||
```typescript
|
||
export enum ChatUserRole {
|
||
OWNER = 'owner', // Владелец чата
|
||
ADMIN = 'admin', // Администратор
|
||
SUPERVISOR = 'supervisor', // Супервайзер
|
||
USER = 'user', // Обычный пользователь
|
||
EXTERNAL = 'external' // Внешний пользователь
|
||
}
|
||
```
|
||
|
||
### DTOs для создания чатов
|
||
|
||
#### CreatePersonalChatDto
|
||
```typescript
|
||
export class CreatePersonalChatDto {
|
||
@ApiProperty({ description: 'Provider ID' })
|
||
@IsNumber()
|
||
providerId: number;
|
||
|
||
@ApiProperty({ description: 'User ID of chat companion' })
|
||
@IsNumber()
|
||
companionId: number;
|
||
}
|
||
```
|
||
|
||
**Пример запроса:**
|
||
```json
|
||
{
|
||
"providerId": 1,
|
||
"companionId": 123
|
||
}
|
||
```
|
||
|
||
#### CreateGroupChatDto
|
||
```typescript
|
||
export class CreateGroupChatDto {
|
||
@ApiProperty({ description: 'Provider ID' })
|
||
@IsNumber()
|
||
providerId: number;
|
||
|
||
@ApiProperty({ description: 'Chat title' })
|
||
@IsString()
|
||
title: string;
|
||
|
||
@ApiProperty({ description: 'User IDs of chat participants', type: [Number] })
|
||
@IsArray()
|
||
participantIds: number[];
|
||
|
||
@ApiPropertyOptional({ description: 'Entity ID associated with the chat', nullable: true })
|
||
@IsOptional()
|
||
@IsNumber()
|
||
entityId?: number | null;
|
||
}
|
||
```
|
||
|
||
**Пример запроса:**
|
||
```json
|
||
{
|
||
"providerId": 1,
|
||
"title": "Проект Alpha",
|
||
"participantIds": [123, 456, 789],
|
||
"entityId": 1001
|
||
}
|
||
```
|
||
|
||
### DTOs для сообщений
|
||
|
||
#### SendChatMessageDto
|
||
```typescript
|
||
export class SendChatMessageDto {
|
||
@ApiPropertyOptional()
|
||
@IsOptional()
|
||
@IsNumber()
|
||
replyToId?: number | null;
|
||
|
||
@ApiProperty()
|
||
@IsString()
|
||
text: string;
|
||
|
||
@ApiPropertyOptional({ nullable: true })
|
||
@IsOptional()
|
||
@IsArray()
|
||
fileIds?: string[] | null;
|
||
}
|
||
```
|
||
|
||
**Пример запроса:**
|
||
```json
|
||
{
|
||
"text": "Привет, как дела?",
|
||
"replyToId": 123,
|
||
"fileIds": ["file_123", "file_456"]
|
||
}
|
||
```
|
||
|
||
## База данных
|
||
|
||
### Основные таблицы
|
||
|
||
#### chat
|
||
```sql
|
||
CREATE TABLE public.chat (
|
||
id integer NOT NULL,
|
||
provider_id integer NOT NULL,
|
||
created_by integer,
|
||
external_id character varying,
|
||
type character varying NOT NULL,
|
||
title character varying,
|
||
entity_id integer,
|
||
account_id integer NOT NULL,
|
||
created_at timestamp without time zone NOT NULL
|
||
);
|
||
```
|
||
|
||
#### chat_message
|
||
```sql
|
||
CREATE TABLE public.chat_message (
|
||
id integer NOT NULL,
|
||
chat_id integer NOT NULL,
|
||
chat_user_id integer NOT NULL,
|
||
external_id character varying,
|
||
text text,
|
||
account_id integer NOT NULL,
|
||
created_at timestamp without time zone NOT NULL,
|
||
reply_to_id integer
|
||
);
|
||
```
|
||
|
||
#### chat_user
|
||
```sql
|
||
CREATE TABLE public.chat_user (
|
||
id integer NOT NULL,
|
||
chat_id integer NOT NULL,
|
||
user_id integer,
|
||
role character varying NOT NULL,
|
||
account_id integer NOT NULL
|
||
);
|
||
```
|
||
|
||
#### chat_provider
|
||
```sql
|
||
CREATE TABLE public.chat_provider (
|
||
id integer NOT NULL,
|
||
created_by integer,
|
||
type character varying NOT NULL,
|
||
title character varying NOT NULL,
|
||
account_id integer NOT NULL,
|
||
created_at timestamp without time zone NOT NULL,
|
||
status character varying NOT NULL,
|
||
transport character varying NOT NULL
|
||
);
|
||
```
|
||
|
||
#### chat_message_reaction
|
||
```sql
|
||
CREATE TABLE public.chat_message_reaction (
|
||
id integer NOT NULL,
|
||
message_id integer NOT NULL,
|
||
chat_user_id integer NOT NULL,
|
||
reaction character varying NOT NULL,
|
||
account_id integer NOT NULL,
|
||
created_at timestamp without time zone NOT NULL
|
||
);
|
||
```
|
||
|
||
#### chat_message_user_status
|
||
```sql
|
||
CREATE TABLE public.chat_message_user_status (
|
||
chat_id integer NOT NULL,
|
||
message_id integer NOT NULL,
|
||
chat_user_id integer NOT NULL,
|
||
status character varying NOT NULL,
|
||
account_id integer NOT NULL,
|
||
created_at timestamp without time zone NOT NULL
|
||
);
|
||
```
|
||
|
||
## Frontend
|
||
|
||
### Архитектура компонентов
|
||
|
||
Система чата построена на базе React с использованием контекстов для управления состоянием и кастомных хуков для работы с API.
|
||
|
||
#### MultichatProvider
|
||
Контекст-провайдер для глобального управления состоянием чата в приложении.
|
||
|
||
**Ключевые свойства:**
|
||
```typescript
|
||
interface MultichatContextValue {
|
||
// Видимость модальных окон
|
||
opened: boolean;
|
||
pageOpened: boolean;
|
||
hide: () => void;
|
||
toggle: () => void;
|
||
|
||
// Активный чат
|
||
activeChatId: number | null;
|
||
activeProviderId: number | null;
|
||
setActiveChatId: (id: number | null) => void;
|
||
setActiveProviderId: (id: number | null) => void;
|
||
|
||
// Управление сообщениями
|
||
editMessageId: number | null;
|
||
replyToId: number | null;
|
||
setEditMessageId: (id: number | null) => void;
|
||
setReplyToId: (id: number | null) => void;
|
||
|
||
// Навигация
|
||
show: (params: { activeChatId?: number; activeProviderId?: number }) => void;
|
||
openPage: (returnUrl?: string) => void;
|
||
closePage: () => void;
|
||
}
|
||
```
|
||
|
||
**Использование:**
|
||
```tsx
|
||
const { show, opened, activeChatId } = useMultichatContext();
|
||
|
||
// Открыть чат
|
||
show({ activeChatId: 123, activeProviderId: 1 });
|
||
|
||
// Проверить видимость
|
||
if (opened) {
|
||
// Показать модальное окно
|
||
}
|
||
```
|
||
|
||
#### MultichatModal
|
||
Модальное окно для отображения интерфейса чата в модальном режиме.
|
||
|
||
**Особенности:**
|
||
- Перетаскиваемое и изменяемое по размеру
|
||
- Адаптивный дизайн
|
||
- Сохранение позиции и размера
|
||
- Интеграция с основным UI
|
||
|
||
**Props:**
|
||
```typescript
|
||
interface MultichatModalProps {
|
||
children?: ReactNode;
|
||
modalBounds?: DraggableResizableControlBounds;
|
||
setModalBounds?: (bounds: DraggableResizableControlBounds) => void;
|
||
}
|
||
```
|
||
|
||
#### MultichatControl
|
||
Основной контейнер управления чатом с боковой панелью и областью сообщений.
|
||
|
||
**Структура:**
|
||
```
|
||
MultichatControl
|
||
├── MultichatControlHeader (при модальном просмотре)
|
||
├── ChatsSidebar
|
||
│ ├── ChatList
|
||
│ ├── CreateAmworkChatButton
|
||
│ └── ProviderSelector
|
||
└── ChatMessagesPanel
|
||
├── ChatMessagesPanelHeader
|
||
├── ChatMessagesList
|
||
└── SendChatMessageBlock
|
||
```
|
||
|
||
#### ChatMessagesPanel
|
||
Компонент для отображения и управления сообщениями в чате.
|
||
|
||
**Ключевые функции:**
|
||
- Ленивая загрузка сообщений
|
||
- Автоматическая прокрутка к новым сообщениям
|
||
- Отметка сообщений как прочитанные
|
||
- Обработка реакций и ответов
|
||
|
||
**Props:**
|
||
```typescript
|
||
interface ChatMessagesPanelProps {
|
||
activeChatId: number;
|
||
mobileView?: boolean;
|
||
providers?: ChatProvider[];
|
||
activeProvider?: ChatProvider;
|
||
modalWidth?: string;
|
||
onBack?: () => void;
|
||
}
|
||
```
|
||
|
||
#### SendChatMessageBlock
|
||
Компонент для отправки сообщений с поддержкой различных типов контента.
|
||
|
||
**Возможности:**
|
||
- Текстовые сообщения
|
||
- Файловые вложения (drag & drop)
|
||
- Ответы на сообщения
|
||
- Редактирование сообщений
|
||
- Отправка через Enter или кнопку
|
||
|
||
**Состояния:**
|
||
- Обычное сообщение
|
||
- Ответ на сообщение
|
||
- Редактирование сообщения
|
||
- Отправка файла
|
||
|
||
### Кастомные хуки
|
||
|
||
#### useGetChat
|
||
Получение данных о конкретном чате.
|
||
|
||
```typescript
|
||
const { data: chat, isLoading, error } = useGetChat(chatId);
|
||
```
|
||
|
||
#### useGetChatMessages
|
||
Получение сообщений чата с пагинацией.
|
||
|
||
```typescript
|
||
const {
|
||
data: messages,
|
||
hasNextPage,
|
||
fetchNextPage,
|
||
isLoading
|
||
} = useGetChatMessages(chatId);
|
||
```
|
||
|
||
#### useSendChatMessage
|
||
Отправка сообщений в чат.
|
||
|
||
```typescript
|
||
const { mutateAsync: sendMessage, isPending } = useSendChatMessage({
|
||
chatId,
|
||
providerId
|
||
});
|
||
|
||
// Отправка
|
||
await sendMessage({
|
||
text: "Привет!",
|
||
fileIds: ["file_123"]
|
||
});
|
||
```
|
||
|
||
#### useCreateGroupChat
|
||
Создание группового чата.
|
||
|
||
```typescript
|
||
const { mutateAsync: createChat } = useCreateGroupChat();
|
||
|
||
// Создание
|
||
const newChat = await createChat({
|
||
providerId: 1,
|
||
title: "Новый проект",
|
||
participantIds: [123, 456]
|
||
});
|
||
```
|
||
|
||
### API интеграция
|
||
|
||
#### ChatApi
|
||
Основной API клиент для работы с чатами:
|
||
|
||
```typescript
|
||
export const chatApi = {
|
||
// Получение чатов
|
||
getChats: (params: CursorPagingQuery) =>
|
||
api.get('/chat/chats', { params }),
|
||
|
||
getChat: (chatId: number) =>
|
||
api.get(`/chat/chats/${chatId}`),
|
||
|
||
// Создание чатов
|
||
createPersonalChat: (data: CreatePersonalChatDto) =>
|
||
api.post('/chat/chats/personal', data),
|
||
|
||
createGroupChat: (data: CreateGroupChatDto) =>
|
||
api.post('/chat/chats/group', data),
|
||
|
||
createExternalChat: (data: CreateExternalChatDto) =>
|
||
api.post('/chat/chats/external', data),
|
||
|
||
// Поиск чатов
|
||
findChats: (params: { filter: ChatFindFilterDto; paging: PagingQuery }) =>
|
||
api.get('/chat/chats/find', { params }),
|
||
|
||
findChatsFull: (params: { filter: ChatFindFilterDto; paging: PagingQuery }) =>
|
||
api.get('/chat/chats/find/full', { params }),
|
||
|
||
// Сообщения
|
||
getChatMessages: (chatId: number, params: ChatMessagesFilterDto & PagingQuery) =>
|
||
api.get(`/chat/chats/${chatId}/messages`, { params }),
|
||
|
||
sendMessage: (chatId: number, data: SendChatMessageDto) =>
|
||
api.post(`/chat/chats/${chatId}/messages`, data),
|
||
|
||
updateMessage: (chatId: number, messageId: number, data: SendChatMessageDto) =>
|
||
api.put(`/chat/chats/${chatId}/messages/${messageId}`, data),
|
||
|
||
deleteMessage: (chatId: number, messageId: number) =>
|
||
api.delete(`/chat/chats/${chatId}/messages/${messageId}`),
|
||
|
||
// Статусы
|
||
markAllChatMessagesAsRead: (chatId: number) =>
|
||
api.put(`/chat/chats/${chatId}/status/read`),
|
||
|
||
updateMessageStatus: (chatId: number, messageId: number, status: ChatMessageStatus) =>
|
||
api.put(`/chat/chats/${chatId}/messages/${messageId}/status/${status}`),
|
||
|
||
// Реакции
|
||
reactToMessage: (chatId: number, messageId: number, reaction: string) =>
|
||
api.put(`/chat/chats/${chatId}/messages/${messageId}/react/${reaction}`),
|
||
|
||
unreactToMessage: (chatId: number, messageId: number, reactionId: number) =>
|
||
api.put(`/chat/chats/${chatId}/messages/${messageId}/unreact/${reactionId}`),
|
||
|
||
// Управление чатами
|
||
updateGroupChat: (chatId: number, data: UpdateGroupChatDto) =>
|
||
api.patch(`/chat/chats/group/${chatId}`, data),
|
||
|
||
deleteChat: (chatId: number) =>
|
||
api.delete(`/chat/chats/${chatId}`),
|
||
|
||
pinMessage: (chatId: number, messageId: number) =>
|
||
api.put(`/chat/chats/${chatId}/pin/${messageId}`),
|
||
|
||
unpinMessage: (chatId: number, messageId: number) =>
|
||
api.put(`/chat/chats/${chatId}/unpin/${messageId}`),
|
||
|
||
// Провайдеры
|
||
getChatProviders: () =>
|
||
api.get('/chat/providers'),
|
||
|
||
// Счетчики
|
||
getUnseenCount: () =>
|
||
api.get('/chat/unseen-count'),
|
||
};
|
||
```
|
||
|
||
### React Query интеграция
|
||
|
||
Система использует React Query для кеширования и синхронизации данных:
|
||
|
||
```typescript
|
||
// Ключи запросов
|
||
export const MULTICHAT_QUERY_KEYS = {
|
||
chats: ['multichat', 'chats'] as const,
|
||
chat: (chatId: number) => ['multichat', 'chats', chatId] as const,
|
||
chatMessages: (chatId: number) => ['multichat', 'chats', chatId, 'messages'] as const,
|
||
chatProviders: ['multichat', 'providers'] as const,
|
||
unseenCount: ['multichat', 'unseen-count'] as const,
|
||
} as const;
|
||
|
||
// Инвалидация кеша
|
||
queryClient.invalidateQueries({ queryKey: MULTICHAT_QUERY_KEYS.chats });
|
||
```
|
||
|
||
### Стилизация
|
||
|
||
Компоненты используют styled-components с поддержкой тем:
|
||
|
||
```typescript
|
||
const Root = styled.div`
|
||
background-color: var(--graphite-graphite-20);
|
||
border-radius: 8px;
|
||
|
||
${p => p.$mobileView && css`
|
||
width: calc(100vw - var(--sidebar-width));
|
||
`}
|
||
`;
|
||
```
|
||
|
||
### Обработка событий
|
||
|
||
Система поддерживает real-time обновления через WebSocket события:
|
||
|
||
```typescript
|
||
// Обработчик событий чата
|
||
const chatEventHandler = {
|
||
subscribe: (context: MultichatContextValue) => {
|
||
// Подписка на события
|
||
},
|
||
|
||
unsubscribe: () => {
|
||
// Отписка от событий
|
||
}
|
||
};
|
||
```
|
||
|
||
## Android приложение
|
||
|
||
### Архитектура
|
||
|
||
Приложение построено на базе **MVVM** архитектуры с использованием **Android Architecture Components**:
|
||
|
||
- **Activities/Fragments** - представление данных
|
||
- **ViewModels** - бизнес-логика и управление состоянием
|
||
- **Repositories** - абстракция доступа к данным
|
||
- **Retrofit** - HTTP клиент для API
|
||
- **Room** - локальная база данных (опционально)
|
||
- **WorkManager** - фоновая синхронизация
|
||
|
||
### Основные Activity
|
||
|
||
#### ChatActivity
|
||
Основная активность для отображения чата с полным жизненным циклом управления.
|
||
|
||
**Жизненный цикл:**
|
||
```kotlin
|
||
override fun onCreate(savedInstanceState: Bundle?) {
|
||
super.onCreate(savedInstanceState)
|
||
binding = ActivityChatBinding.inflate(layoutInflater)
|
||
setContentView(binding.root)
|
||
|
||
// Инициализация ViewModel
|
||
viewModel.initialize(serverUrl, token, apiKey, chatId)
|
||
|
||
// Настройка UI компонентов
|
||
setupViews()
|
||
observeViewModel()
|
||
observeAddressBook()
|
||
}
|
||
|
||
override fun onResume() {
|
||
super.onResume()
|
||
viewModel.setChatActive(true) // Активация чата
|
||
}
|
||
|
||
override fun onPause() {
|
||
super.onPause()
|
||
viewModel.setChatActive(false) // Деактивация чата
|
||
}
|
||
```
|
||
|
||
**Основные функции:**
|
||
- Отображение списка сообщений с `RecyclerView`
|
||
- Отправка текстовых сообщений
|
||
- Отправка файлов через `FilePicker`
|
||
- Ответы на сообщения с визуальным индикатором
|
||
- Автоматическая отметка сообщений как прочитанные при прокрутке
|
||
- Управление прикрепленными файлами
|
||
|
||
**Ключевые компоненты:**
|
||
- `MessageAdapter` - адаптер для `RecyclerView` с поддержкой разных типов сообщений
|
||
- `ChatViewModel` - управление состоянием чата и бизнес-логикой
|
||
- `NotificationHelper` - обработка push-уведомлений
|
||
- `BackgroundSyncWorker` - фоновая синхронизация
|
||
|
||
#### MainActivity
|
||
Главная активность с навигацией и списком чатов.
|
||
|
||
**Особенности:**
|
||
- `BottomNavigationView` для переключения разделов
|
||
- `SwipeRefreshLayout` для обновления списка чатов
|
||
- `FloatingActionButton` для создания новых чатов
|
||
- Управление жизненным циклом всех чатов
|
||
|
||
#### CreateChatActivity
|
||
Активность для создания новых чатов с пошаговым мастером.
|
||
|
||
**Этапы создания:**
|
||
1. Выбор типа чата (персональный/групповой)
|
||
2. Выбор участников через `UserSelectionActivity`
|
||
3. Настройка параметров чата
|
||
4. Создание и переход к чату
|
||
|
||
### ViewModels
|
||
|
||
#### ChatViewModel
|
||
Управляет состоянием конкретного чата и сообщениями.
|
||
|
||
**Состояния:**
|
||
```kotlin
|
||
sealed class ChatState {
|
||
object Loading : ChatState()
|
||
data class Success(val chat: Chat) : ChatState()
|
||
data class Error(val message: String) : ChatState()
|
||
}
|
||
|
||
sealed class MessagesState {
|
||
object Loading : MessagesState()
|
||
data class Success(val messages: List<ChatMessage>) : MessagesState()
|
||
data class Error(val message: String) : MessagesState()
|
||
}
|
||
|
||
sealed class SendMessageState {
|
||
object Loading : SendMessageState()
|
||
object Success : SendMessageState()
|
||
data class Error(val message: String) : SendMessageState()
|
||
}
|
||
```
|
||
|
||
**Ключевые методы:**
|
||
```kotlin
|
||
// Загрузка данных чата
|
||
fun refreshChat()
|
||
|
||
// Загрузка сообщений
|
||
fun refreshMessages()
|
||
|
||
// Отправка сообщения
|
||
fun sendMessage(text: String, replyToMessage: ChatMessage?, fileIds: List<String>? = null)
|
||
|
||
// Отправка файла
|
||
fun sendFileMessage(uri: Uri, contentResolver: ContentResolver)
|
||
|
||
// Отметка как прочитанное
|
||
fun markMessagesAsRead()
|
||
|
||
// Управление активностью чата
|
||
fun setChatActive(active: Boolean)
|
||
```
|
||
|
||
#### MainViewModel
|
||
Глобальная ViewModel для управления приложением.
|
||
|
||
**Функции:**
|
||
- Управление списком чатов
|
||
- Кеширование данных пользователей (адресная книга)
|
||
- Синхронизация состояния между активностями
|
||
- Управление подключением к серверу
|
||
|
||
**Ключевые методы:**
|
||
```kotlin
|
||
// Получение имени пользователя
|
||
fun getUserName(userId: Long): String
|
||
|
||
// Получение URL аватара
|
||
fun getUserAvatarUrl(userId: Long): String?
|
||
|
||
// Уведомление об обновлении чата
|
||
fun notifyChatUpdated(chatId: Long)
|
||
|
||
// Управление адресной книгой
|
||
fun refreshAddressBook()
|
||
```
|
||
|
||
### Репозитории
|
||
|
||
#### ChatRepository
|
||
Репозиторий для работы с API чатов с поддержкой кеширования.
|
||
|
||
**Основные методы:**
|
||
```kotlin
|
||
suspend fun getChats(): Result<List<Chat>>
|
||
suspend fun getChat(chatId: Long): Result<Chat>
|
||
suspend fun getMessages(chatId: Long, limit: Int = 50, offset: Long? = null): Result<List<ChatMessage>>
|
||
suspend fun sendMessage(chatId: Long, message: SendChatMessageDto): Result<ChatMessage>
|
||
suspend fun uploadFile(uri: Uri, contentResolver: ContentResolver): Result<List<UploadedFile>>
|
||
suspend fun markAsRead(chatId: Long): Result<Unit>
|
||
suspend fun updateMessageStatus(chatId: Long, messageId: Long, status: ChatMessageStatus): Result<Unit>
|
||
```
|
||
|
||
**Обработка ошибок:**
|
||
```kotlin
|
||
sealed class Result<T> {
|
||
data class Success<T>(val data: T) : Result<T>()
|
||
data class Failure<T>(val exception: Throwable) : Result<T>()
|
||
}
|
||
```
|
||
|
||
### Модели данных
|
||
|
||
#### ChatRequests.kt
|
||
DTO для запросов к API:
|
||
|
||
```kotlin
|
||
data class CreatePersonalChatRequest(
|
||
val providerId: Long,
|
||
val companionId: Long
|
||
)
|
||
|
||
data class CreateGroupChatRequest(
|
||
val providerId: Long,
|
||
val title: String,
|
||
val participantIds: List<Long>,
|
||
val entityId: Long? = null
|
||
)
|
||
|
||
data class UpdateGroupChatRequest(
|
||
val title: String? = null,
|
||
val participantIds: List<Long>? = null
|
||
)
|
||
```
|
||
|
||
#### ChatModels.kt
|
||
Основные модели данных приложения:
|
||
|
||
```kotlin
|
||
data class Chat(
|
||
val id: Long,
|
||
val providerId: Long,
|
||
val type: String,
|
||
val title: String?,
|
||
val users: List<ChatUserItem>,
|
||
val lastMessage: ChatMessage?,
|
||
val unseenCount: Int,
|
||
val createdAt: String
|
||
)
|
||
|
||
data class ChatMessage(
|
||
val id: Long,
|
||
val chatId: Long,
|
||
val chatUserId: Long,
|
||
val text: String?,
|
||
val replyToId: Long?,
|
||
val files: List<ChatMessageFile>,
|
||
val reactions: List<ChatMessageReaction>,
|
||
val status: ChatMessageStatus,
|
||
val createdAt: String
|
||
)
|
||
|
||
data class ChatUserItem(
|
||
val id: Long,
|
||
val userId: Long?,
|
||
val name: String?,
|
||
val avatarUrl: String?
|
||
)
|
||
```
|
||
|
||
### UI Компоненты
|
||
|
||
#### MessageAdapter
|
||
Адаптер для отображения сообщений в `RecyclerView`.
|
||
|
||
**Типы ViewHolder:**
|
||
- `TextMessageViewHolder` - текстовые сообщения
|
||
- `FileMessageViewHolder` - сообщения с файлами
|
||
- `ReplyMessageViewHolder` - ответы на сообщения
|
||
- `SystemMessageViewHolder` - системные сообщения
|
||
|
||
**Особенности:**
|
||
- Поддержка разных типов сообщений через `getItemViewType()`
|
||
- Оптимизация производительности с `DiffUtil`
|
||
- Обработка кликов и долгого нажатия
|
||
- Анимации для новых сообщений
|
||
|
||
#### MessageAdapter.kt
|
||
```kotlin
|
||
class MessageAdapter(
|
||
private val getUserName: (Long) -> String,
|
||
private val chatUsers: List<ChatUserItem>?,
|
||
private val onScrollToMessage: (Long) -> Unit,
|
||
private val onReplyToMessage: (ChatMessage) -> Unit,
|
||
private val serverUrl: String,
|
||
private val token: String?,
|
||
private val apiKey: String?
|
||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||
|
||
private val messages = mutableListOf<ChatMessage>()
|
||
|
||
fun submitList(newMessages: List<ChatMessage>) {
|
||
val diffResult = DiffUtil.calculateDiff(MessageDiffCallback(messages, newMessages))
|
||
messages.clear()
|
||
messages.addAll(newMessages)
|
||
diffResult.dispatchUpdatesTo(this)
|
||
}
|
||
}
|
||
```
|
||
|
||
### Background Sync
|
||
|
||
#### BackgroundSyncWorker
|
||
Worker для фоновой синхронизации данных.
|
||
|
||
**Функции:**
|
||
- Периодическая проверка новых сообщений
|
||
- Обновление уведомлений
|
||
- Синхронизация аватаров пользователей
|
||
- Обработка оффлайн режима
|
||
|
||
**Конфигурация:**
|
||
```kotlin
|
||
val syncWork = PeriodicWorkRequestBuilder<BackgroundSyncWorker>(
|
||
15, TimeUnit.MINUTES // Синхронизация каждые 15 минут
|
||
)
|
||
.addConstraints(
|
||
Constraints.Builder()
|
||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||
.build()
|
||
)
|
||
.build()
|
||
```
|
||
|
||
### Уведомления
|
||
|
||
#### NotificationHelper
|
||
Управление push-уведомлениями и локальными уведомлениями.
|
||
|
||
**Функции:**
|
||
- Создание уведомлений о новых сообщениях
|
||
- Группировка уведомлений по чатам
|
||
- Кастомные звуки и вибрация
|
||
- Deep linking в конкретные чаты
|
||
|
||
```kotlin
|
||
fun showMessageNotification(
|
||
chatId: Long,
|
||
message: ChatMessage,
|
||
senderName: String,
|
||
chatTitle: String
|
||
) {
|
||
val intent = Intent(context, ChatActivity::class.java).apply {
|
||
putExtra("chat_id", chatId)
|
||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||
}
|
||
|
||
val pendingIntent = PendingIntent.getActivity(
|
||
context, chatId.toInt(), intent, PendingIntent.FLAG_IMMUTABLE
|
||
)
|
||
|
||
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
|
||
.setSmallIcon(R.drawable.ic_message)
|
||
.setContentTitle(chatTitle)
|
||
.setContentText("$senderName: ${message.text}")
|
||
.setContentIntent(pendingIntent)
|
||
.setAutoCancel(true)
|
||
.build()
|
||
|
||
notificationManager.notify(chatId.toInt(), notification)
|
||
}
|
||
```
|
||
|
||
### Работа с файлами
|
||
|
||
#### File Handling
|
||
Обработка загрузки и отображения файлов.
|
||
|
||
**Поддерживаемые операции:**
|
||
- Выбор файлов через `Intent.ACTION_GET_CONTENT`
|
||
- Загрузка файлов на сервер
|
||
- Отображение превью изображений
|
||
- Скачивание файлов
|
||
- Управление разрешениями на хранение
|
||
|
||
```kotlin
|
||
private fun uploadFileAndSendMessage(message: String) {
|
||
selectedFileUri?.let { uri ->
|
||
lifecycleScope.launch {
|
||
try {
|
||
val result = ChatRepository(apiClient).uploadFile(uri, contentResolver)
|
||
result.fold(
|
||
onSuccess = { files ->
|
||
viewModel.sendMessage(message, replyingToMessage, files.map { it.id })
|
||
clearFileSelection()
|
||
},
|
||
onFailure = { error ->
|
||
Toast.makeText(this@ChatActivity, "Upload failed: ${error.message}", Toast.LENGTH_SHORT).show()
|
||
}
|
||
)
|
||
} catch (e: Exception) {
|
||
Toast.makeText(this@ChatActivity, "Error: ${e.message}", Toast.LENGTH_SHORT).show()
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### Темизация и локализация
|
||
|
||
**Поддержка тем:**
|
||
- Светлая тема
|
||
- Темная тема
|
||
- Системная тема
|
||
|
||
**Локализация:**
|
||
- Русский язык
|
||
- Английский язык
|
||
- Поддержка RTL для арабского и иврита
|
||
|
||
### Производительность
|
||
|
||
**Оптимизации:**
|
||
- ViewBinding вместо findViewById
|
||
- DiffUtil для RecyclerView
|
||
- Кеширование изображений с Glide
|
||
- Пейджинг для больших списков
|
||
- Background threading для тяжелых операций
|
||
|
||
## Интеграция с внешними провайдерами
|
||
|
||
### Поддерживаемые провайдеры
|
||
|
||
1. **Facebook Messenger**
|
||
- Требует: `page_id`, `page_access_token`, `user_access_token`
|
||
- Транспорт: `messenger`
|
||
|
||
2. **Telegram**
|
||
- Интеграция через Telegram API
|
||
|
||
3. **WhatsApp (Twilio)**
|
||
- Требует: `account_sid`, `auth_token`, `phone_number`
|
||
- Транспорт: `whatsapp`
|
||
|
||
4. **Whazzup**
|
||
- Требует: `api_key`, `channel_id`, `plain_id`
|
||
- Транспорт: `whatsapp`
|
||
|
||
### Настройка провайдеров
|
||
|
||
Провайдеры настраиваются через интерфейс интеграций в разделе "Settings > Integrations".
|
||
|
||
## Безопасность
|
||
|
||
### Аутентификация
|
||
- JWT токены для API запросов
|
||
- Проверка прав доступа на уровне аккаунта
|
||
- Валидация принадлежности пользователя к чату
|
||
|
||
### Авторизация
|
||
- Проверка доступа к чатам
|
||
- Ролевая модель для групповых чатов
|
||
- Ограничение действий с сообщениями
|
||
|
||
## Real-time обновления
|
||
|
||
### WebSocket
|
||
- Автоматическое обновление сообщений
|
||
- Статусы прочтения в реальном времени
|
||
- Уведомления о новых сообщениях
|
||
|
||
### Background Sync (Android)
|
||
- `BackgroundSyncWorker` для синхронизации в фоне
|
||
- Обновление уведомлений
|
||
- Синхронизация аватаров пользователей
|
||
|
||
## Файловые вложения
|
||
|
||
### Поддерживаемые форматы
|
||
- Изображения, документы, видео, аудио
|
||
- Максимальный размер файла настраивается
|
||
|
||
### Загрузка файлов
|
||
- Backend: хранение в файловом хранилище
|
||
- Frontend: drag & drop, выбор файла
|
||
- Android: выбор из галереи/файлового менеджера
|
||
|
||
### Безопасность файлов
|
||
- Валидация MIME типов
|
||
- Сканирование на вирусы (опционально)
|
||
- Ограничение доступа по ссылкам
|
||
|
||
## Поиск и фильтрация
|
||
|
||
### Поиск чатов
|
||
- По имени пользователя (персональные чаты)
|
||
- По содержимому сообщений
|
||
- По связанным сущностям
|
||
- Фильтрация по провайдеру
|
||
|
||
### Полнотекстовый поиск
|
||
- Индексы в базе данных
|
||
- Поиск по тексту сообщений
|
||
- Поддержка регулярных выражений
|
||
|
||
## Мониторинг и логирование
|
||
|
||
### Метрики
|
||
- Количество активных чатов
|
||
- Статистика сообщений
|
||
- Производительность API
|
||
|
||
### Логи
|
||
- Детальное логирование API запросов
|
||
- Ошибки интеграции с провайдерами
|
||
- Аудит действий пользователей
|
||
|
||
## Расширение системы
|
||
|
||
### Добавление нового провайдера
|
||
1. Создать конфигурацию провайдера в базе данных
|
||
2. Реализовать интеграцию в `ChatProviderService`
|
||
3. Добавить DTO для настроек
|
||
4. Создать миграцию для новой таблицы провайдера
|
||
5. Обновить UI для настройки
|
||
|
||
### Кастомизация UI
|
||
- Темизация компонентов
|
||
- Кастомные аватары
|
||
- Дополнительные поля в сообщениях
|
||
|
||
## Заключение
|
||
|
||
Система чата предоставляет полнофункциональное решение для коммуникации в CRM, поддерживая множественные каналы связи, real-time обновления и интеграцию с бизнес-логикой системы. Архитектура обеспечивает масштабируемость, безопасность и гибкость для будущих расширений.
|