В рамках мессенджера необходимо реализовать систему "Presence 2.0". Это не просто индикатор online/offline, а полноценная система статусов с медиа-контентом, временем жизни (TTL) и возможностью планирования распорядка дня.
User Stories
Как пользователь, я хочу устанавливать текстовый или медиа-статус (эмодзи, гиф) на определенное время, чтобы контакты знали, чем я занят.
Как пользователь, я хочу составить расписание своего дня (например, "В дороге", "Работа", "Обед", "Тренировка"), чтобы статусы менялись автоматически.
Как пользователь, я хочу иметь возможность перебить текущее расписание разовым статусом, который по истечении времени вернет меня к моему обычному графику.
Functional Requirements
Status Types: Text, Emoji, Media (GIF/Video via MinIO).
TTL Support: Каждый статус имеет expires_at.
Scheduling: Поддержка повторяющихся правил (ежедневно, по будням, конкретные дни месяца).
Real-time: Обновление статуса у всех подписчиков (друзей/собеседников) без перезагрузки страницы.
Priority Logic: Ручной статус имеет приоритет над запланированным.
Acceptance Criteria (AC)
Созданный статус сохраняется в PostgreSQL (структура) и MinIO (медиа).
SignalR корректно рассылает UserStatusChanged ивент при смене статуса.
Система автоматически переключает статус по расписанию с точностью до минуты.
При удалении/завершении "перебивающего" статуса система возвращает актуальный статус по расписанию.
Database Schema Design
Для обеспечения высокой производительности и гибкости используется гибридный подход.
PostgreSQL (Relational Core)
Используется для хранения структуры расписаний и текущих состояний.
-- Библиотека шаблонов или созданные пользователем "быстрые" статусы
CREATETABLEpresence.status_templates(idUUIDPRIMARYKEY,user_idUUIDNULL,-- NULL для системных (Default)
titleVARCHAR(100),emojiVARCHAR(10),media_idUUID,-- Ссылка на метаданные в MongoDB/MinIO
default_duration_minutesINT);-- Настройки расписания пользователя
CREATETABLEpresence.user_schedules(idUUIDPRIMARYKEY,user_idUUIDNOTNULLINDEX,nameVARCHAR(255),cron_expressionVARCHAR(100),-- Для сложных повторений (каждое 1-е число и т.д.)
is_activeBOOLEANDEFAULTtrue);-- Элементы, из которых состоит день
CREATETABLEpresence.schedule_items(idUUIDPRIMARYKEY,schedule_idUUIDREFERENCESpresence.user_schedules(id)ONDELETECASCADE,template_idUUIDREFERENCESpresence.status_templates(id),start_timeTIMENOTNULL,-- Время начала (например, 09:00:00)
end_timeTIMENOTNULL-- Время окончания
);-- Текущее состояние (Single Source of Truth для быстрого чтения)
CREATETABLEpresence.current_presence(user_idUUIDPRIMARYKEY,current_status_idUUID,custom_textVARCHAR(255),expires_atTIMESTAMPWITHTIMEZONE,is_overrideBOOLEANDEFAULTfalse,-- Флаг: установлен ли статус вручную "поверх" плана
last_updatedTIMESTAMPWITHTIMEZONEDEFAULTNOW());//Collection:StatusMedia{"_id":"UUID","minio_bucket":"user-content","object_key":"statuses/gifs/working_cat.gif","mime_type":"image/gif","attributes":{"width":500,"height":500,"is_animated":true}}
3. Status Lifecycle & Logic Pipeline
Status Lifecycle & Business Logic
Состояния статуса
Scheduled: Статус находится в базе расписания, ждет своего времени.
Active (Planned): Статус активен, так как наступило время по расписанию.
Active (Override): Статус установлен пользователем вручную. Игнорирует текущее расписание до наступления expires_at.
Expired: Время жизни вышло. Система триггерит процесс Re-evaluate.
Cleared: Статус отсутствует (Online/Offline по умолчанию).
Логика переключения (Pipeline)
Trigger: Срабатывание таймера (Background Job) или ручное действие пользователя.
Evaluation:
Если это ручная установка: записать в current_presence, поставить is_override = true, вычислить expires_at.
Если это окончание TTL: проверить user_schedules на наличие подходящего по времени schedule_item.
SignalR Hub рассылает сообщение подписчикам в группу user_presence_{userId}.
Media Delivery: Фронтенд получает URL медиа-файла (через API-прокси или напрямую pre-signed URL из MinIO).
Backend Implementation Guide (Modular Monolith)
Domain Layer (DDD)
Aggregate Root: UserPresence.
Value Objects: ScheduleRule (инкапсулирует логику Cron или интервалов), StatusContent (текст + ссылка на медиа).
Domain Service: PresenceEvaluator — содержит логику определения "Какой статус должен быть у пользователя прямо сейчас?".
Application Layer (MediatR / CQRS)
Commands:
SetManualStatusCommand: установка разового статуса.
DefineScheduleCommand: создание/обновление распорядка дня.
ResetStatusCommand: принудительный сброс к расписанию.
Queries:
GetPresenceByUserIdQuery: высокопроизводительный запрос (Read Model из Postgres).
Infrastructure Layer
Background Jobs: Рекомендуется Quartz.NET.
При установке любого статуса с TTL создается/обновляется Job.
Для расписаний — постоянно работающий воркер, который раз в минуту сверяет текущее время с schedule_items для активных пользователей (или использует оптимизированную очередь событий).
SignalR:
Интеграция в PresenceModule.
Метод NotifyStatusChanged(Guid userId, StatusDto newStatus).
Integration with MinIO
Загрузка медиа происходит через MediaModule.
При установке статуса проверяется существование media_id.
API выдает фронтенду готовую ссылку на объект.
## Context
В рамках мессенджера необходимо реализовать систему "Presence 2.0". Это не просто индикатор online/offline, а полноценная система статусов с медиа-контентом, временем жизни (TTL) и возможностью планирования распорядка дня.
## User Stories
1. **Как пользователь**, я хочу устанавливать текстовый или медиа-статус (эмодзи, гиф) на определенное время, чтобы контакты знали, чем я занят.
2. **Как пользователь**, я хочу составить расписание своего дня (например, "В дороге", "Работа", "Обед", "Тренировка"), чтобы статусы менялись автоматически.
3. **Как пользователь**, я хочу иметь возможность перебить текущее расписание разовым статусом, который по истечении времени вернет меня к моему обычному графику.
## Functional Requirements
- **Status Types**: Text, Emoji, Media (GIF/Video via MinIO).
- **TTL Support**: Каждый статус имеет `expires_at`.
- **Scheduling**: Поддержка повторяющихся правил (ежедневно, по будням, конкретные дни месяца).
- **Real-time**: Обновление статуса у всех подписчиков (друзей/собеседников) без перезагрузки страницы.
- **Priority Logic**: Ручной статус имеет приоритет над запланированным.
## Acceptance Criteria (AC)
- Созданный статус сохраняется в PostgreSQL (структура) и MinIO (медиа).
- SignalR корректно рассылает `UserStatusChanged` ивент при смене статуса.
- Система автоматически переключает статус по расписанию с точностью до минуты.
- При удалении/завершении "перебивающего" статуса система возвращает актуальный статус по расписанию.
# Database Schema Design
Для обеспечения высокой производительности и гибкости используется гибридный подход.
## PostgreSQL (Relational Core)
Используется для хранения структуры расписаний и текущих состояний.
```sql
-- Библиотека шаблонов или созданные пользователем "быстрые" статусы
CREATE TABLE presence.status_templates (
id UUID PRIMARY KEY,
user_id UUID NULL, -- NULL для системных (Default)
title VARCHAR(100),
emoji VARCHAR(10),
media_id UUID, -- Ссылка на метаданные в MongoDB/MinIO
default_duration_minutes INT
);
-- Настройки расписания пользователя
CREATE TABLE presence.user_schedules (
id UUID PRIMARY KEY,
user_id UUID NOT NULL INDEX,
name VARCHAR(255),
cron_expression VARCHAR(100), -- Для сложных повторений (каждое 1-е число и т.д.)
is_active BOOLEAN DEFAULT true
);
-- Элементы, из которых состоит день
CREATE TABLE presence.schedule_items (
id UUID PRIMARY KEY,
schedule_id UUID REFERENCES presence.user_schedules(id) ON DELETE CASCADE,
template_id UUID REFERENCES presence.status_templates(id),
start_time TIME NOT NULL, -- Время начала (например, 09:00:00)
end_time TIME NOT NULL -- Время окончания
);
-- Текущее состояние (Single Source of Truth для быстрого чтения)
CREATE TABLE presence.current_presence (
user_id UUID PRIMARY KEY,
current_status_id UUID,
custom_text VARCHAR(255),
expires_at TIMESTAMP WITH TIME ZONE,
is_override BOOLEAN DEFAULT false, -- Флаг: установлен ли статус вручную "поверх" плана
last_updated TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
// Collection: StatusMedia
{
"_id": "UUID",
"minio_bucket": "user-content",
"object_key": "statuses/gifs/working_cat.gif",
"mime_type": "image/gif",
"attributes": {
"width": 500,
"height": 500,
"is_animated": true
}
}
```
---
### 3. Status Lifecycle & Logic Pipeline
# Status Lifecycle & Business Logic
## Состояния статуса
1. **Scheduled**: Статус находится в базе расписания, ждет своего времени.
2. **Active (Planned)**: Статус активен, так как наступило время по расписанию.
3. **Active (Override)**: Статус установлен пользователем вручную. Игнорирует текущее расписание до наступления `expires_at`.
4. **Expired**: Время жизни вышло. Система триггерит процесс `Re-evaluate`.
5. **Cleared**: Статус отсутствует (Online/Offline по умолчанию).
## Логика переключения (Pipeline)
1. **Trigger**: Срабатывание таймера (Background Job) или ручное действие пользователя.
2. **Evaluation**:
- Если это ручная установка: записать в `current_presence`, поставить `is_override = true`, вычислить `expires_at`.
- Если это окончание TTL: проверить `user_schedules` на наличие подходящего по времени `schedule_item`.
3. **Persistence**: Обновление записи в PostgreSQL.
4. **Broadcast**:
- MediatR Notification `UserStatusChangedDomainEvent`.
- SignalR Hub рассылает сообщение подписчикам в группу `user_presence_{userId}`.
5. **Media Delivery**: Фронтенд получает URL медиа-файла (через API-прокси или напрямую pre-signed URL из MinIO).
# Backend Implementation Guide (Modular Monolith)
## Domain Layer (DDD)
- **Aggregate Root**: `UserPresence`.
- **Value Objects**: `ScheduleRule` (инкапсулирует логику Cron или интервалов), `StatusContent` (текст + ссылка на медиа).
- **Domain Service**: `PresenceEvaluator` — содержит логику определения "Какой статус должен быть у пользователя прямо сейчас?".
## Application Layer (MediatR / CQRS)
- **Commands**:
- `SetManualStatusCommand`: установка разового статуса.
- `DefineScheduleCommand`: создание/обновление распорядка дня.
- `ResetStatusCommand`: принудительный сброс к расписанию.
- **Queries**:
- `GetPresenceByUserIdQuery`: высокопроизводительный запрос (Read Model из Postgres).
## Infrastructure Layer
- **Background Jobs**: Рекомендуется **Quartz.NET**.
- При установке любого статуса с TTL создается/обновляется `Job`.
- Для расписаний — постоянно работающий воркер, который раз в минуту сверяет текущее время с `schedule_items` для активных пользователей (или использует оптимизированную очередь событий).
- **SignalR**:
- Интеграция в `PresenceModule`.
- Метод `NotifyStatusChanged(Guid userId, StatusDto newStatus)`.
## Integration with MinIO
- Загрузка медиа происходит через `MediaModule`.
- При установке статуса проверяется существование `media_id`.
- API выдает фронтенду готовую ссылку на объект.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Context
В рамках мессенджера необходимо реализовать систему "Presence 2.0". Это не просто индикатор online/offline, а полноценная система статусов с медиа-контентом, временем жизни (TTL) и возможностью планирования распорядка дня.
User Stories
Functional Requirements
expires_at.Acceptance Criteria (AC)
UserStatusChangedивент при смене статуса.Database Schema Design
Для обеспечения высокой производительности и гибкости используется гибридный подход.
PostgreSQL (Relational Core)
Используется для хранения структуры расписаний и текущих состояний.
3. Status Lifecycle & Logic Pipeline
Status Lifecycle & Business Logic
Состояния статуса
expires_at.Re-evaluate.Логика переключения (Pipeline)
current_presence, поставитьis_override = true, вычислитьexpires_at.user_schedulesна наличие подходящего по времениschedule_item.UserStatusChangedDomainEvent.user_presence_{userId}.Backend Implementation Guide (Modular Monolith)
Domain Layer (DDD)
UserPresence.ScheduleRule(инкапсулирует логику Cron или интервалов),StatusContent(текст + ссылка на медиа).PresenceEvaluator— содержит логику определения "Какой статус должен быть у пользователя прямо сейчас?".Application Layer (MediatR / CQRS)
SetManualStatusCommand: установка разового статуса.DefineScheduleCommand: создание/обновление распорядка дня.ResetStatusCommand: принудительный сброс к расписанию.GetPresenceByUserIdQuery: высокопроизводительный запрос (Read Model из Postgres).Infrastructure Layer
Job.schedule_itemsдля активных пользователей (или использует оптимизированную очередь событий).PresenceModule.NotifyStatusChanged(Guid userId, StatusDto newStatus).Integration with MinIO
MediaModule.media_id.