Модуль связей и Klipy

This commit is contained in:
Халимов Рустам
2026-03-27 01:22:20 +03:00
parent 7ef73b414c
commit b399649cfd
31 changed files with 603 additions and 472 deletions

View File

@@ -0,0 +1,35 @@
using Knot.Modules.Relations.Domain;
using System;
using System.Threading;
using System.Threading.Tasks;
using Knot.Shared.Kernel;
using Knot.Modules.Relations.Application.Abstractions;
using Microsoft.EntityFrameworkCore;
namespace Knot.Modules.Relations.Application.Contacts;
public record AcceptContactRequestCommand(Guid UserId, Guid ContactId) : ICommand<Guid>;
internal sealed class AcceptContactRequestCommandHandler : ICommandHandler<AcceptContactRequestCommand, Guid>
{
private readonly IContactsDbContext _context;
public AcceptContactRequestCommandHandler(IContactsDbContext context)
{
_context = context;
}
public async Task<Result<Guid>> Handle(AcceptContactRequestCommand request, CancellationToken cancellationToken)
{
var contact = await _context.Contacts.FirstOrDefaultAsync(c => c.Id == request.ContactId, cancellationToken);
if (contact == null || contact.ContactId != request.UserId)
{
return Result.Failure<Guid>(new Error("Contacts.NotFound", "Contact request not found."));
}
contact.Accept();
await _context.SaveChangesAsync(cancellationToken);
return Result.Success(contact.Id);
}
}

View File

@@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Knot.Modules.Relations.Domain;
using Knot.Shared.Kernel;
using Microsoft.EntityFrameworkCore;
using Knot.Modules.Relations.Application.Abstractions;
namespace Knot.Modules.Relations.Application.Contacts;
public record ContactDto(
Guid Id,
string Username,
string DisplayName,
string Avatar,
bool IsOnline,
DateTime? LastSeen,
Guid RelationId,
bool IsBlocked = false,
bool IsExternal = false,
string? Domain = null);
public record GetContactsQuery(Guid UserId) : IQuery<List<ContactDto>>;
internal sealed class GetContactsQueryHandler : IQueryHandler<GetContactsQuery, List<ContactDto>>
{
private readonly IContactsDbContext _context;
public GetContactsQueryHandler(IContactsDbContext context)
{
_context = context;
}
public async Task<Result<List<ContactDto>>> Handle(GetContactsQuery request, CancellationToken cancellationToken)
{
var relations = await _context.Contacts
.Where(c => (c.UserId == request.UserId || c.ContactId == request.UserId) && c.Status == ContactStatus.Accepted)
.ToListAsync(cancellationToken);
var contactIds = relations.Select(c => c.UserId == request.UserId ? c.ContactId : c.UserId).ToList();
var replicas = await _context.UserReplicas
.Where(r => contactIds.Contains(r.Id))
.ToListAsync(cancellationToken);
var result = new List<ContactDto>();
foreach (var replica in replicas)
{
var rel = relations.First(c => c.UserId == replica.Id || c.ContactId == replica.Id);
result.Add(new ContactDto(
replica.Id,
replica.Username,
replica.DisplayName,
replica.Avatar,
false, // Presence handled by another service or real-time
null,
rel.Id,
rel.Status == ContactStatus.Blocked,
replica.IsExternal,
replica.Domain
));
}
return Result.Success(result);
}
}

View File

@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Knot.Modules.Relations.Domain;
using Knot.Shared.Kernel;
using Microsoft.EntityFrameworkCore;
using Knot.Modules.Relations.Application.Abstractions;
namespace Knot.Modules.Relations.Application.Contacts;
public record ContactUserDto(Guid Id, string Username, string DisplayName, string Avatar);
public record ContactRequestDto(Guid Id, ContactUserDto User, DateTime CreatedAt);
public record GetIncomingRequestsQuery(Guid UserId) : IQuery<List<ContactRequestDto>>;
internal sealed class GetIncomingRequestsQueryHandler : IQueryHandler<GetIncomingRequestsQuery, List<ContactRequestDto>>
{
private readonly IContactsDbContext _context;
public GetIncomingRequestsQueryHandler(IContactsDbContext context)
{
_context = context;
}
public async Task<Result<List<ContactRequestDto>>> Handle(GetIncomingRequestsQuery request, CancellationToken cancellationToken)
{
var contacts = await _context.Contacts
.Where(c => c.ContactId == request.UserId && c.Status == ContactStatus.Pending)
.ToListAsync(cancellationToken);
var requesterIds = contacts.Select(c => c.UserId).ToList();
var replicas = await _context.UserReplicas
.Where(r => requesterIds.Contains(r.Id))
.ToDictionaryAsync(r => r.Id, cancellationToken);
var result = contacts
.Where(c => replicas.ContainsKey(c.UserId))
.Select(c =>
{
var user = replicas[c.UserId];
return new ContactRequestDto(
c.Id,
new ContactUserDto(user.Id, user.Username, user.DisplayName, user.Avatar),
c.CreatedAt
);
})
.ToList();
return Result.Success(result);
}
}

View File

@@ -0,0 +1,43 @@
using Knot.Modules.Relations.Domain;
using System;
using System.Threading;
using System.Threading.Tasks;
using Knot.Shared.Kernel;
using Knot.Modules.Relations.Application.Abstractions;
using Microsoft.EntityFrameworkCore;
namespace Knot.Modules.Relations.Application.Contacts;
public record RemoveContactCommand(Guid UserId, Guid ContactRelationId) : ICommand;
internal sealed class RemoveContactCommandHandler : ICommandHandler<RemoveContactCommand>
{
private readonly IContactsDbContext _context;
public ContactRelationNotFound() : base("Contacts.NotFound", "Contact relation not found.") { }
public ContactRelationUnauthorized() : base("Contacts.Unauthorized", "You are not authorized to remove this contact.") { }
public RemoveContactCommandHandler(IContactsDbContext context)
{
_context = context;
}
public async Task<Result> Handle(RemoveContactCommand request, CancellationToken cancellationToken)
{
var contact = await _context.Contacts.FirstOrDefaultAsync(c => c.Id == request.ContactRelationId, cancellationToken);
if (contact == null)
{
return Result.Failure(new Error("Contacts.NotFound", "Contact relation not found."));
}
if (contact.UserId != request.UserId && contact.ContactId != request.UserId)
{
return Result.Failure(new Error("Contacts.Unauthorized", "You are not authorized to remove this contact."));
}
_context.Contacts.Remove(contact);
await _context.SaveChangesAsync(cancellationToken);
return Result.Success();
}
}

View File

@@ -0,0 +1,44 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Knot.Modules.Relations.Domain;
using Knot.Shared.Kernel;
using Microsoft.EntityFrameworkCore;
using Knot.Modules.Relations.Application.Abstractions;
namespace Knot.Modules.Relations.Application.Contacts;
public record SendContactRequestCommand(Guid UserId, Guid ContactId) : ICommand;
internal sealed class SendContactRequestCommandHandler : ICommandHandler<SendContactRequestCommand>
{
private readonly IContactsDbContext _context;
public SendContactRequestCommandHandler(IContactsDbContext context)
{
_context = context;
}
public async Task<Result> Handle(SendContactRequestCommand request, CancellationToken cancellationToken)
{
if (request.UserId == request.ContactId)
{
return Result.Failure(new Error("Contacts.Self", "You cannot add yourself to contacts."));
}
var existing = await _context.Contacts
.FirstOrDefaultAsync(f => (f.UserId == request.UserId && f.ContactId == request.ContactId) ||
(f.UserId == request.ContactId && f.ContactId == request.UserId), cancellationToken);
if (existing != null)
{
return Result.Failure(new Error("Contacts.Exists", "Contact relation already exists."));
}
var contact = Contact.CreateRequest(request.UserId, request.ContactId);
_context.Contacts.Add(contact);
await _context.SaveChangesAsync(cancellationToken);
return Result.Success();
}
}