Files
forkmessager/backend/src/Modules/Messaging/Infrastructure/Persistence/MessageRepository.cs
Халимов Рустам 852efa090e Опросы
2026-04-07 11:11:35 +03:00

213 lines
7.8 KiB
C#

using System.Text.RegularExpressions;
using Knot.Contracts.Messaging.Application.Abstractions;
using Knot.Contracts.Messaging.Domain;
using Knot.Shared.Kernel;
using Microsoft.Extensions.Configuration;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
namespace Knot.Modules.Messaging.Infrastructure.Persistence;
public sealed class MessageRepository : IMessageRepository
{
private readonly IMongoCollection<Message> _messages;
private readonly IChatAccessProvider _chatAccessProvider;
private readonly MediatR.IMediator _mediator;
public MessageRepository(IMongoDatabase mongoDatabase, IChatAccessProvider chatAccessProvider, MediatR.IMediator mediator)
{
_messages = mongoDatabase.GetCollection<Message>("messages");
_chatAccessProvider = chatAccessProvider;
_mediator = mediator;
}
public void Add(Message message)
{
_messages.InsertOne(message);
// Publish domain events manualy for mongo entities
var events = message.GetDomainEvents().ToList();
message.ClearDomainEvents();
// This runs synchronously or without waiting, better to run async but Add is void
// In this implementation setting, fire and forget or wrap sync
foreach (var domainEvent in events)
{
_mediator.Publish(domainEvent).GetAwaiter().GetResult();
}
}
public async Task<Message?> GetByIdAsync(Guid id, CancellationToken cancellationToken)
{
var filter = Builders<Message>.Filter.Eq(m => m.Id, id);
return await _messages.Find(filter).FirstOrDefaultAsync(cancellationToken);
}
public async Task<List<Message>> GetChatMessagesAsync(Guid chatId, int limit, int offset, CancellationToken cancellationToken)
{
var filter = Builders<Message>.Filter.Eq(m => m.ChatId, chatId);
return await _messages.Find(filter)
.SortByDescending(m => m.CreatedAt)
.Skip(offset)
.Limit(limit)
.ToListAsync(cancellationToken);
}
public async Task<Message?> GetLatestChatMessageAsync(Guid chatId, CancellationToken cancellationToken)
{
var filter = Builders<Message>.Filter.Eq(m => m.ChatId, chatId);
return await _messages.Find(filter)
.SortByDescending(m => m.CreatedAt)
.FirstOrDefaultAsync(cancellationToken);
}
public async Task<List<Message>> GetPinnedMessagesAsync(Guid chatId, CancellationToken cancellationToken)
{
var builder = Builders<Message>.Filter;
var filter = builder.And(
builder.Eq(m => m.ChatId, chatId),
builder.BitsAnySet(m => m.State, (long)MessageState.IsPinned)
);
return await _messages.Find(filter)
.SortByDescending(m => m.CreatedAt)
.ToListAsync(cancellationToken);
}
public async Task<List<Message>> GetChatMessagesCursorAsync(Guid chatId, DateTime? cursor, long? sequenceId, int limit, CancellationToken cancellationToken)
{
var builder = Builders<Message>.Filter;
var filter = builder.Eq(m => m.ChatId, chatId);
if (sequenceId.HasValue)
{
filter &= builder.Lt(m => m.SequenceId, sequenceId.Value);
}
else if (cursor.HasValue)
{
filter &= builder.Lt(m => m.CreatedAt, cursor.Value);
}
return await _messages.Find(filter)
.SortByDescending(m => m.SequenceId)
.Limit(limit)
.ToListAsync(cancellationToken);
}
public async Task<List<Message>> GetChatMessagesAroundAsync(Guid chatId, long sequenceId, int limit, CancellationToken cancellationToken)
{
var builder = Builders<Message>.Filter;
// Target message
var targetFilter = builder.And(builder.Eq(m => m.ChatId, chatId), builder.Eq(m => m.SequenceId, sequenceId));
var targetMsg = await _messages.Find(targetFilter).FirstOrDefaultAsync(cancellationToken);
// Older messages
var olderFilter = builder.And(builder.Eq(m => m.ChatId, chatId), builder.Lt(m => m.SequenceId, sequenceId));
var older = await _messages.Find(olderFilter)
.SortByDescending(m => m.SequenceId)
.Limit(limit / 2)
.ToListAsync(cancellationToken);
// Newer messages
var newerFilter = builder.And(builder.Eq(m => m.ChatId, chatId), builder.Gt(m => m.SequenceId, sequenceId));
var newer = await _messages.Find(newerFilter)
.SortBy(m => m.SequenceId)
.Limit(limit / 2)
.ToListAsync(cancellationToken);
var result = new List<Message>();
result.AddRange(older);
if (targetMsg != null) result.Add(targetMsg);
result.AddRange(newer);
return result.OrderBy(m => m.SequenceId).ToList();
}
public async Task<List<Message>> SearchMessagesAsync(string query, Guid? chatId, Guid requestingUserId, CancellationToken cancellationToken)
{
// Not ideal for SQL/Mongo combination but keeping the signature
var validChatIdsQuery = await _chatAccessProvider.GetValidChatIdsForUserAsync(requestingUserId, cancellationToken);
var builder = Builders<Message>.Filter;
var filter = builder.In(m => m.ChatId, validChatIdsQuery);
if (chatId.HasValue)
{
filter &= builder.Eq(m => m.ChatId, chatId.Value);
}
var textFilter = Builders<Message>.Filter.Regex("Content", new BsonRegularExpression(Regex.Escape(query), "i"));
filter &= textFilter;
return await _messages.Find(filter)
.SortByDescending(m => m.CreatedAt)
.Limit(50)
.ToListAsync(cancellationToken);
}
public async Task<Message?> GetLastStoryMessageAsync(Guid chatId, Guid storyId, CancellationToken cancellationToken)
{
var filter = Builders<Message>.Filter.And(
Builders<Message>.Filter.Eq(m => m.ChatId, chatId),
Builders<Message>.Filter.Eq("_t", "StoryMessage"),
Builders<Message>.Filter.Eq("StoryId", storyId)
);
return await _messages.Find(filter)
.SortByDescending(m => m.CreatedAt)
.FirstOrDefaultAsync(cancellationToken);
}
public async Task UpdateAsync(Message message, CancellationToken cancellationToken)
{
var filter = Builders<Message>.Filter.Eq(m => m.Id, message.Id);
await _messages.ReplaceOneAsync(filter, message, new ReplaceOptions { IsUpsert = true }, cancellationToken);
// Publish domain events
var events = message.GetDomainEvents().ToList();
message.ClearDomainEvents();
foreach (var domainEvent in events)
{
await _mediator.Publish(domainEvent, cancellationToken);
}
}
public async Task<List<Message>> GetAllMessagesAsync(CancellationToken cancellationToken)
{
return await _messages.Find(_ => true).ToListAsync(cancellationToken);
}
public async Task DeleteChatMessagesAsync(Guid chatId, CancellationToken cancellationToken)
{
var filter = Builders<Message>.Filter.Eq(m => m.ChatId, chatId);
await _messages.DeleteManyAsync(filter, cancellationToken);
}
public async Task DeleteUserMessagesAsync(Guid userId, CancellationToken cancellationToken)
{
var filter = Builders<Message>.Filter.Eq(m => m.SenderId, userId);
await _messages.DeleteManyAsync(filter, cancellationToken);
}
public async Task RemoveAsync(Guid id, CancellationToken cancellationToken)
{
var filter = Builders<Message>.Filter.Eq(m => m.Id, id);
await _messages.DeleteOneAsync(filter, cancellationToken);
}
public async Task DeleteAsync(Message message, CancellationToken cancellationToken)
{
var filter = Builders<Message>.Filter.Eq(m => m.Id, message.Id);
await _messages.DeleteOneAsync(filter, cancellationToken);
}
}