Структура, доп модули, федерация, документация
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Knot.Modules.Messaging.Domain;
|
||||
|
||||
public class PollMessage : Message
|
||||
{
|
||||
public override string Type => "poll";
|
||||
public override string? Content { get; protected set; } // Вопрос опроса
|
||||
|
||||
public bool IsAnonymous { get; protected set; }
|
||||
public bool AllowMultipleAnswers { get; protected set; }
|
||||
public bool IsClosed { get; protected set; }
|
||||
public DateTime? ExpiresAt { get; protected set; }
|
||||
|
||||
private List<PollOption> _options = new();
|
||||
public IReadOnlyCollection<PollOption> Options => _options.AsReadOnly();
|
||||
|
||||
private List<PollVote> _votes = new();
|
||||
public IReadOnlyCollection<PollVote> Votes => _votes.AsReadOnly();
|
||||
|
||||
// Инфраструктурный конструктор
|
||||
private PollMessage() : base() { }
|
||||
|
||||
public PollMessage(
|
||||
Guid id,
|
||||
Guid chatId,
|
||||
Guid senderId,
|
||||
string question,
|
||||
List<string> options,
|
||||
bool isAnonymous,
|
||||
bool allowMultipleAnswers,
|
||||
DateTime? expiresAt,
|
||||
Guid? replyToId,
|
||||
Guid? forwardedFromId,
|
||||
DateTime createdAt,
|
||||
bool isImported)
|
||||
: base(id, chatId, senderId, replyToId, forwardedFromId, createdAt, isImported)
|
||||
{
|
||||
Content = question;
|
||||
IsAnonymous = isAnonymous;
|
||||
AllowMultipleAnswers = allowMultipleAnswers;
|
||||
ExpiresAt = expiresAt;
|
||||
IsClosed = false;
|
||||
|
||||
foreach (var optionText in options)
|
||||
{
|
||||
_options.Add(new PollOption(Guid.NewGuid(), Id, optionText));
|
||||
}
|
||||
|
||||
if (!isImported)
|
||||
{
|
||||
// Можно добавить специфичное событие для опроса
|
||||
// RaiseDomainEvent(new PollCreatedDomainEvent(Id, ChatId, SenderId, question));
|
||||
}
|
||||
}
|
||||
|
||||
public void Vote(Guid userId, List<Guid> optionIds)
|
||||
{
|
||||
if (IsClosed) throw new InvalidOperationException("Poll is closed");
|
||||
if (ExpiresAt.HasValue && DateTime.UtcNow > ExpiresAt.Value)
|
||||
{
|
||||
Close();
|
||||
throw new InvalidOperationException("Poll has expired");
|
||||
}
|
||||
|
||||
if (optionIds == null || !optionIds.Any()) throw new ArgumentException("At least one option must be selected");
|
||||
if (!AllowMultipleAnswers && optionIds.Count > 1) throw new ArgumentException("Multiple answers are not allowed");
|
||||
|
||||
// Проверяем существование опций
|
||||
foreach (var oid in optionIds)
|
||||
{
|
||||
if (!_options.Any(o => o.Id == oid)) throw new ArgumentException($"Option {oid} not found");
|
||||
}
|
||||
|
||||
// Удаляем старые голоса пользователя
|
||||
_votes.RemoveAll(v => v.UserId == userId);
|
||||
|
||||
// Добавляем новые голоса
|
||||
foreach (var oid in optionIds)
|
||||
{
|
||||
_votes.Add(new PollVote(Id, userId, oid, DateTime.UtcNow));
|
||||
}
|
||||
|
||||
// Обновляем статистику в опциях
|
||||
RecalculateResults();
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
IsClosed = true;
|
||||
}
|
||||
|
||||
private void RecalculateResults()
|
||||
{
|
||||
int totalVotesCount = _votes.Select(v => v.UserId).Distinct().Count();
|
||||
|
||||
foreach (var option in _options)
|
||||
{
|
||||
int optionVotes = _votes.Count(v => v.OptionId == option.Id);
|
||||
double percentage = totalVotesCount > 0 ? (double)optionVotes / totalVotesCount * 100 : 0;
|
||||
option.UpdateResults(optionVotes, (int)Math.Round(percentage));
|
||||
}
|
||||
}
|
||||
|
||||
public override void Delete()
|
||||
{
|
||||
// При удалении опроса можно очистить голоса или оставить для истории (зависит от политики)
|
||||
base.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
public class PollOption
|
||||
{
|
||||
public Guid Id { get; private set; }
|
||||
public Guid PollId { get; private set; }
|
||||
public string Text { get; private set; }
|
||||
|
||||
// Денормализованные данные для быстрой отдачи
|
||||
public int VotesCount { get; private set; }
|
||||
public int Percentage { get; private set; }
|
||||
|
||||
private PollOption() { } // EF
|
||||
|
||||
public PollOption(Guid id, Guid pollId, string text)
|
||||
{
|
||||
Id = id;
|
||||
PollId = pollId;
|
||||
Text = text;
|
||||
}
|
||||
|
||||
public void UpdateResults(int count, int percentage)
|
||||
{
|
||||
VotesCount = count;
|
||||
Percentage = percentage;
|
||||
}
|
||||
}
|
||||
|
||||
public class PollVote
|
||||
{
|
||||
public Guid PollId { get; private set; }
|
||||
public Guid UserId { get; private set; }
|
||||
public Guid OptionId { get; private set; }
|
||||
public DateTime VotedAt { get; private set; }
|
||||
|
||||
private PollVote() { } // EF
|
||||
|
||||
public PollVote(Guid pollId, Guid userId, Guid optionId, DateTime votedAt)
|
||||
{
|
||||
PollId = pollId;
|
||||
UserId = userId;
|
||||
OptionId = optionId;
|
||||
VotedAt = votedAt;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user