@qor/chat-layer
@qor/chat-layer
Чат-мессенджер для платформы QOR — real-time обмен сообщениями через Socket.IO + Prisma/SQLite.
Nuxt Layer для встраивания чат-мессенджера в приложения QOR. Telegram-стиль интерфейс с поддержкой личных и групповых чатов, вложений, реакций, поиска и виртуального скролла.
Архитектура
┌─────────────────────────────────────────────┐
│ App (extends @qor/chat-layer) │
│ │
│ ┌──────────────┐ ┌───────────────────┐ │
│ │ Vue Client │ │ Nitro Server │ │
│ │ │ │ │ │
│ │ Socket.IO ◄──┼───┤ Socket.IO Server │ │
│ │ Client │ │ (Engine.IO) │ │
│ │ │ │ │ │
│ │ REST API ◄───┼───┤ REST Endpoints │ │
│ │ ($fetch) │ │ /api/v1/chat/* │ │
│ └──────────────┘ │ │ │
│ │ Prisma + SQLite │ │
│ └───────────────────┘ │
└─────────────────────────────────────────────┘
Conditional Server
По аналогии с @qor/auth-layer, чат поддерживает два режима работы:
| Режим | chatApiBase | Описание |
|---|---|---|
| Локальный | '' (пусто) | Nitro поднимает REST API + Socket.IO сервер, Prisma пишет в SQLite |
| Внешний | 'https://api.example.com' | Серверные роуты исключаются через nitro:config хук, клиент проксирует запросы на внешний API |
Стек технологий
| Слой | Технология |
|---|---|
| Транспорт | Socket.IO 4.8 (WebSocket + polling fallback) |
| База данных | Prisma 6 + SQLite |
| Клиент | Vue 3 + Nuxt 4 + Nuxt UI 4 |
| Виртуальный скролл | vue-virtual-scroller 2.0.0-beta.8 |
| Markdown | marked + isomorphic-dompurify |
| Утилиты | @vueuse/core (createSharedComposable) |
Возможности
Мессенджер
- Личные и групповые чаты
- Текстовые сообщения с Markdown-разметкой
- Вложения: изображения, аудио, видео, документы
- Голосовые сообщения (запись через MediaRecorder)
- Ответы на сообщения (reply-to)
- Редактирование и удаление сообщений
- Пересылка сообщений между чатами
- Реакции эмодзи
- Закрепление сообщений
- Drag & drop файлов
Real-time
- Статус доставки: sending → sent → delivered → read
- Индикатор набора текста (typing)
- Онлайн/оффлайн статус пользователей
- Авто-реконнект с синхронизацией пропущенных сообщений
UI
- Dual-panel (desktop) + full-screen (mobile)
- Виртуальный скролл для списков чатов и сообщений
- Поиск по чатам и сообщениям
- Лайтбокс для изображений
- Панель информации о чате
- Safe-area поддержка (Capacitor/iOS)
Структура файлов
layers/chat/
├── prisma/
│ └── schema.prisma # 7 моделей: User, Conversation, Participant, Message, Attachment, Reaction, ReadReceipt
├── app/
│ ├── components/ # 27 Vue-компонентов
│ ├── composables/ # 17 composables
│ ├── plugins/
│ │ ├── socket.client.ts # Создание Socket.IO клиента
│ │ ├── chat-auth.client.ts # Мост auth → chat: авто-подключение
│ │ └── virtual-scroller.client.ts
│ ├── pages/
│ │ └── chats/ # /chats (список), /chats/:id (диалог)
│ ├── types/
│ │ ├── chat.ts # Доменные типы
│ │ └── socket.ts # Типизированные Socket.IO события
│ └── utils/ # Форматирование, Markdown, хелперы
├── server/
│ ├── plugins/
│ │ └── socket.io.ts # Engine.IO + Socket.IO сервер
│ ├── api/v1/chat/ # REST эндпоинты
│ ├── routes/uploads/ # Статика загруженных файлов
│ └── utils/ # Prisma, auth, emitter
├── nuxt.config.ts
└── package.json
Модели данных
Prisma-схема
model ChatUser {
id String @id @default(uuid())
externalId String @unique // ID из внешней системы авторизации
displayName String?
avatarUrl String?
lastSeen DateTime?
}
model ChatConversation {
id String @id @default(uuid())
type String // "single" | "group"
name String? // Название группы
}
model ChatMessage {
id String @id @default(uuid())
conversationId String
senderId String
type String @default("text") // text|image|file|audio|video|system
body String @default("")
replyToId String?
editedAt DateTime?
deletedAt DateTime?
createdAt DateTime @default(now())
}
model ChatAttachment {
id String @id @default(uuid())
messageId String? // null = orphan (ещё не привязан)
type String // image|file|audio|video
url String
name String
size Int
mimeType String?
}
model ChatReaction {
id String @id @default(uuid())
messageId String
userId String
emoji String
@@unique([messageId, userId, emoji])
}
model ChatReadReceipt {
id String @id @default(uuid())
messageId String
userId String
readAt DateTime @default(now())
@@unique([messageId, userId])
}
Зависимости
Чат-layer наследует @qor/auth-layer (который наследует @qor/base-layer):
@qor/chat-layer
└── @qor/auth-layer
└── @qor/base-layer
Требуется наличие useAuth() composable из auth-layer для автоматического подключения к чату.