Модуль историй с документацией

This commit is contained in:
Халимов Рустам
2026-03-23 01:43:27 +03:00
parent 6e532b021d
commit 7cb6ac61dd
34 changed files with 392 additions and 918 deletions

View File

@@ -0,0 +1,84 @@
# Модуль Историй (Stories) - Техническая Документация
## 1. Обзор
**Модуль Историй** отвечает за создание, просмотр и предоставление ленты временных публикаций. Любая история доступна для просмотра в ленте ровно 24 часа с момента её публикации, после чего автоматически исчезает.
Модуль построен на архитектуре **CQRS** с использованием паттерна MediatR, а публичный REST API реализован через Minimal APIs с помощью библиотеки Carter.
*Важная деталь предметной области:* Ответы на истории и быстрые реакции обрабатываются модулем `Conversations/Messaging` и доставляются автору в личные сообщения (Direct Messages) как специальный тип сообщения ([StoryMessage](file:///e:/GIT/forkmessager/backend/src/Modules/Messaging/Infrastructure/Persistence/MessageRepository.cs#104-116)). Сам модуль Stories отвечает исключительно за контент, жизненный цикл и учет просмотров истории.
## 2. Гибридная Архитектура Хранения
Для обеспечения отказоустойчивости при масштабировании, модуль использует **Гибридную модель данных**, распределяя нагрузку между двумя базами:
### A. MongoDB (Документное хранилище)
Используется как основное хранилище сверхбыстрых данных документа [Story](file:///e:/GIT/forkmessager/backend/src/Modules/Stories/Domain/Story.cs#17-26) ([IStoryRepository](file:///e:/GIT/forkmessager/backend/src/Modules/Stories/Application/Abstractions/IStoryRepository.cs#9-19)).
* **Молниеносное чтение:** Отсутствие тяжелых SQL-связей (джоинов) позволяет мгновенно формировать ленты историй для пользователей. Актуальность истории вычисляется автоматически при выборке путем сравнения времени создания с текущим.
* **Атомарные операции:** Счетчик просмотров ([ViewsCount](file:///e:/GIT/forkmessager/backend/src/Modules/Stories/Domain/Story.cs#32-36)) обновляется быстрой In-Place атомарной операцией `$inc`, что полностью исключает блокировки записей (locking) при массовых просмотрах.
* **Нативное шифрование (E2EE):** Чувствительные текстовые поля (такие как `Content` и `MediaUrl`) автоматически и прозрачно шифруются алгоритмом AES-256 прямо перед записью в базу через кастомный [EncryptedStringSerializer](file:///e:/GIT/forkmessager/backend/src/Modules/Stories/Infrastructure/Persistence/Mongo/EncryptedStringSerializer.cs#8-50), внедрённый на уровне BSON-маппинга.
### B. PostgreSQL (Реляционная база данных)
Используется для хранения точных логов просмотров [StoryViewers](file:///e:/GIT/forkmessager/backend/src/Modules/Stories/Application/Stories/Queries/GetStoryViewers/GetStoryViewersQuery.cs#22-23) (через [StoriesDbContext](file:///e:/GIT/forkmessager/backend/src/Modules/Stories/Infrastructure/Database/StoriesDbContext.cs#10-13)).
* **Точечный контроль:** Хранение идентификаторов уникальных зрителей (`ViewerId`) в реляционной БД предотвращает раздувание BSON-документов MongoDB.
* **Строгая целостность:** Таблица просмотров содержит составной уникальный индекс (`StoryId`, [UserId](file:///e:/GIT/forkmessager/backend/src/Host/Program.cs#201-205)), что на уровне самой базы гарантирует защиту от "накрутки" — один человек засчитывается в просмотры истории только один раз.
---
## 3. Модели Данных
### Документ [Story](file:///e:/GIT/forkmessager/backend/src/Modules/Stories/Domain/Story.cs#17-26) (в MongoDB)
* **Id (Guid):** Уникальный внутренний ключ.
* **UserId (Guid):** Создатель истории.
* **Type (StoryType Enum):** Тип контента: `Text = 0`, `Image = 1` или `Video = 2`.
* **MediaUrl (string, зашифровано!):** Ссылка на привязанный S3-файл.
* **Content (string, зашифровано!):** Содержимое текстовой истории или подпись к медиа.
* **BgColor (string, опционально):** Цвет фона для текстового типа.
* **CreatedAt (DateTime):** Точка отсчета жизни истории. На основе этого времени "на лету" вычисляется активность истории (обычно `CreatedAt > DateTime.UtcNow.AddHours(-24)`).
* **ViewsCount (int):** Предвычисленный быстрый счетчик уникальных просмотров, готовый к мгновенной выдаче клиентам.
### Сущность [StoryViewer](file:///e:/GIT/forkmessager/backend/src/Modules/Stories/Domain/StoryViewer.cs#11-17) (в PostgreSQL)
* **Id (Guid):** Первичный ключ записи.
* **StoryId (Guid):** Внешний условный ключ профиля истории.
* **UserId (Guid):** Идентификатор посмотревшего.
* **ViewedAt (DateTime):** Точная временная метка факта просмотра.
---
## 4. Хранение Файлов и Безопасность
* **MinIO S3 Хранилище:** Загрузка тяжелого медиа производится через смежный модуль [Storage](file:///e:/GIT/forkmessager/backend/src/Modules/Storage/DependencyInjection.cs#13-35). Файлы именуются по алгоритму SHA-256 (от содержимого файла), что нативно гарантирует абсолютную дедупликацию мемов и роликов на серверах.
* **Шифрование данных (At-Rest):** Все текстовые данные, попадающие в Mongo, зашифрованы сервисом `IEncryptionService`. Дамп базы данных не имеет ценности без ключей окружения приложения.
---
## 5. Описание API Эндпоинтов (`api/stories`)
| Метод | Маршрут | Описание |
| :--- | :--- | :--- |
| `GET` | `/` | Получение гибридной ленты актуальных историй текущих друзей (Mongo+Postgres маппинг). |
| `GET` | `/user/{userId}` | Запрос актуальных историй конкретного активного пользователя. |
| `POST` | `/` | Создание и мгновенная публикация новой истории. |
| `DELETE`| `/{id}` | Удаление собственной истории автором в любой момент времени. |
| `POST` | `/{id}/view` | Фиксация просмотра. Добавляет запись в PostgreSQL и инкрементит MongoDB-счетчик. |
| `GET` | `/{id}/viewers` | Получение списка зрителей истории (функция "свайп вверх" для авторов). |
---
## 6. Real-Time Интеграция (SignalR WebSockets)
Модуль историй поддерживает мгновенное оповещение авторов через WebSockets, встроенное в [ViewStoryCommand](file:///e:/GIT/forkmessager/backend/src/Modules/Stories/Application/Stories/Commands/ViewStory/ViewStoryCommand.cs#22-23).
Логика прямого эфира (когда Юзер B просматривает историю Юзера A):
1. Запрос валидируется через PostgreSQL (ранее не просмотрено).
2. Выполняется мгновенный инкремент `$inc` счетчика в MongoDB.
3. Через `ChatHub` адресно автору истории моментально отправляется событие `story_viewed`.
**Спецификация JSON-события "Мою историю просмотрели":**
```json
{
"storyId": "guid",
"userId": "guid", // Кто именно сейчас посмотрел
"username": "johndoe",
"displayName": "John Doe",
"avatar": "url...",
"viewedAt": "timestamp",
"viewCount": 42, // Актуальный живой счетчик на этот момент!
"ownerId": "guid" // Кому адресован ивент (User A)
}
```
Фронтенд может слушать событие `story_viewed` и анимированно обновлять счетчики "в прямом эфире", а также пополнять список зрителей в попапе истории без дополнительных HTTP-запросов.