# Модуль Историй (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-запросов.