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