Мелкие правки

This commit is contained in:
Халимов Рустам
2026-03-13 00:46:40 +03:00
parent 9daafae9c5
commit 7670a3b787
82 changed files with 725 additions and 114 deletions

View File

@@ -1,8 +1,9 @@
using MediatR;
using Microsoft.AspNetCore.SignalR;
using Vortex.Modules.Chats.Domain;
using Vortex.Modules.Chats.Infrastructure.SignalR;
using Vortex.Shared.Kernel;
using global::Vortex.Modules.Chats.Domain;
using global::Vortex.Modules.Chats.Infrastructure.SignalR;
using global::Vortex.Shared.Kernel;
using global::Vortex.Modules.Chats.Application.Abstractions;
namespace Vortex.Modules.Chats.Application.Messages.Delete;
@@ -10,17 +11,17 @@ public sealed record DeleteMessagesCommand(
Guid ChatId,
Guid UserId,
List<Guid> MessageIds,
bool DeleteForAll) : global::Vortex.Shared.Kernel.ICommand;
bool DeleteForAll) : ICommand;
public sealed class DeleteMessagesCommandHandler : global::Vortex.Shared.Kernel.ICommandHandler<DeleteMessagesCommand>
public sealed class DeleteMessagesCommandHandler : ICommandHandler<DeleteMessagesCommand>
{
private readonly IMessageRepository _messageRepository;
private readonly Vortex.Modules.Chats.Application.Abstractions.IChatsUnitOfWork _unitOfWork;
private readonly IChatsUnitOfWork _unitOfWork;
private readonly IHubContext<ChatHub> _hubContext;
public DeleteMessagesCommandHandler(
IMessageRepository messageRepository,
Vortex.Modules.Chats.Application.Abstractions.IChatsUnitOfWork unitOfWork,
IChatsUnitOfWork unitOfWork,
IHubContext<ChatHub> hubContext)
{
_messageRepository = messageRepository;

View File

@@ -103,6 +103,7 @@ public sealed class ChatMember : Entity<Guid>
public Guid UserId { get; private set; }
public string Role { get; private set; }
public DateTime JoinedAt { get; private set; }
public bool IsPinned { get; private set; }
public bool IsMuted { get; private set; }
// For EF Core
@@ -115,4 +116,6 @@ public sealed class ChatMember : Entity<Guid>
Role = role;
JoinedAt = DateTime.UtcNow;
}
public void TogglePin() => IsPinned = !IsPinned;
}

View File

@@ -1,8 +1,8 @@
using MediatR;
using Microsoft.AspNetCore.SignalR;
using Vortex.Shared.Kernel;
using Vortex.Modules.Chats.Domain;
using Vortex.Modules.Chats.Infrastructure.SignalR;
using Vortex.Shared.Kernel;
using MediatR;
using Microsoft.AspNetCore.SignalR;
namespace Vortex.Modules.Chats.Infrastructure.Handlers;

View File

@@ -61,12 +61,12 @@ public sealed class ChatsDbContext : DbContext, IChatsUnitOfWork
mb.WithOwner().HasForeignKey(x => x.MessageId);
}).Navigation(m => m.Media).UsePropertyAccessMode(PropertyAccessMode.Field);
builder.OwnsMany(m => m.Reactions, mb =>
{
mb.ToTable("MessageReactions");
mb.HasKey(x => x.Id);
mb.HasIndex(x => new { x.MessageId, x.UserId, x.Emoji }).IsUnique();
}).Navigation(m => m.Reactions).UsePropertyAccessMode(PropertyAccessMode.Field);
builder.HasMany(m => m.Reactions)
.WithOne()
.HasForeignKey(x => x.MessageId)
.OnDelete(DeleteBehavior.Cascade);
builder.Navigation(m => m.Reactions).UsePropertyAccessMode(PropertyAccessMode.Field);
builder.PrimitiveCollection(m => m.DeletedByUsers)
.HasColumnName("DeletedByUsers")
@@ -80,6 +80,13 @@ public sealed class ChatsDbContext : DbContext, IChatsUnitOfWork
builder.Navigation(m => m.ReadBy).UsePropertyAccessMode(PropertyAccessMode.Field);
});
modelBuilder.Entity<Reaction>(builder =>
{
builder.ToTable("MessageReactions");
builder.HasKey(r => r.Id);
builder.HasIndex(r => new { r.MessageId, r.UserId, r.Emoji }).IsUnique();
});
modelBuilder.Entity<ReadReceipt>(builder =>
{
builder.ToTable("ReadReceipts");

View File

@@ -0,0 +1,251 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using Vortex.Modules.Chats.Infrastructure.Persistence;
#nullable disable
namespace Vortex.Modules.Chats.Infrastructure.Persistence.Migrations
{
[DbContext(typeof(ChatsDbContext))]
[Migration("20260312204842_SupportPinningAndMuting")]
partial class SupportPinningAndMuting
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("chats")
.HasAnnotation("ProductVersion", "10.0.4")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Vortex.Modules.Chats.Domain.Chat", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Avatar")
.HasColumnType("text");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Name")
.HasColumnType("text");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Chats", "chats");
});
modelBuilder.Entity("Vortex.Modules.Chats.Domain.Message", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Guid>("ChatId")
.HasColumnType("uuid");
b.Property<string>("Content")
.HasColumnType("text");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.PrimitiveCollection<Guid[]>("DeletedByUsers")
.IsRequired()
.HasColumnType("uuid[]")
.HasColumnName("DeletedByUsers");
b.Property<Guid?>("ForwardedFromId")
.HasColumnType("uuid");
b.Property<bool>("IsDeleted")
.HasColumnType("boolean");
b.Property<bool>("IsEdited")
.HasColumnType("boolean");
b.Property<string>("Quote")
.HasColumnType("text");
b.Property<Guid?>("ReplyToId")
.HasColumnType("uuid");
b.Property<Guid>("SenderId")
.HasColumnType("uuid");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Messages", "chats");
});
modelBuilder.Entity("Vortex.Modules.Chats.Domain.Reaction", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Emoji")
.IsRequired()
.HasColumnType("text");
b.Property<Guid>("MessageId")
.HasColumnType("uuid");
b.Property<Guid>("UserId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("MessageId", "UserId", "Emoji")
.IsUnique();
b.ToTable("MessageReactions", "chats");
});
modelBuilder.Entity("Vortex.Modules.Chats.Domain.ReadReceipt", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Guid>("MessageId")
.HasColumnType("uuid");
b.Property<DateTime>("ReadAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid>("UserId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("MessageId", "UserId")
.IsUnique();
b.ToTable("ReadReceipts", "chats");
});
modelBuilder.Entity("Vortex.Modules.Chats.Domain.Chat", b =>
{
b.OwnsMany("Vortex.Modules.Chats.Domain.ChatMember", "Members", b1 =>
{
b1.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b1.Property<Guid>("ChatId")
.HasColumnType("uuid");
b1.Property<bool>("IsMuted")
.HasColumnType("boolean");
b1.Property<bool>("IsPinned")
.HasColumnType("boolean");
b1.Property<DateTime>("JoinedAt")
.HasColumnType("timestamp with time zone");
b1.Property<string>("Role")
.IsRequired()
.HasColumnType("text");
b1.Property<Guid>("UserId")
.HasColumnType("uuid");
b1.HasKey("Id");
b1.HasIndex("ChatId", "UserId")
.IsUnique();
b1.ToTable("ChatMembers", "chats");
b1.WithOwner()
.HasForeignKey("ChatId");
});
b.Navigation("Members");
});
modelBuilder.Entity("Vortex.Modules.Chats.Domain.Message", b =>
{
b.OwnsMany("Vortex.Modules.Chats.Domain.Media", "Media", b1 =>
{
b1.Property<Guid>("MessageId")
.HasColumnType("uuid");
b1.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b1.Property<string>("Filename")
.HasColumnType("text");
b1.Property<long?>("Size")
.HasColumnType("bigint");
b1.Property<string>("Type")
.IsRequired()
.HasColumnType("text");
b1.Property<string>("Url")
.IsRequired()
.HasColumnType("text");
b1.HasKey("MessageId", "Id");
b1.ToTable("MessageMedia", "chats");
b1.WithOwner()
.HasForeignKey("MessageId");
});
b.Navigation("Media");
});
modelBuilder.Entity("Vortex.Modules.Chats.Domain.Reaction", b =>
{
b.HasOne("Vortex.Modules.Chats.Domain.Message", null)
.WithMany("Reactions")
.HasForeignKey("MessageId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Vortex.Modules.Chats.Domain.ReadReceipt", b =>
{
b.HasOne("Vortex.Modules.Chats.Domain.Message", null)
.WithMany("ReadBy")
.HasForeignKey("MessageId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Vortex.Modules.Chats.Domain.Message", b =>
{
b.Navigation("Reactions");
b.Navigation("ReadBy");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,64 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Vortex.Modules.Chats.Infrastructure.Persistence.Migrations
{
/// <inheritdoc />
public partial class SupportPinningAndMuting : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropPrimaryKey(
name: "PK_MessageMedia",
schema: "chats",
table: "MessageMedia");
migrationBuilder.DropIndex(
name: "IX_MessageMedia_MessageId",
schema: "chats",
table: "MessageMedia");
migrationBuilder.AddColumn<bool>(
name: "IsPinned",
schema: "chats",
table: "ChatMembers",
type: "boolean",
nullable: false,
defaultValue: false);
migrationBuilder.AddPrimaryKey(
name: "PK_MessageMedia",
schema: "chats",
table: "MessageMedia",
columns: new[] { "MessageId", "Id" });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropPrimaryKey(
name: "PK_MessageMedia",
schema: "chats",
table: "MessageMedia");
migrationBuilder.DropColumn(
name: "IsPinned",
schema: "chats",
table: "ChatMembers");
migrationBuilder.AddPrimaryKey(
name: "PK_MessageMedia",
schema: "chats",
table: "MessageMedia",
column: "Id");
migrationBuilder.CreateIndex(
name: "IX_MessageMedia_MessageId",
schema: "chats",
table: "MessageMedia",
column: "MessageId");
}
}
}

View File

@@ -94,6 +94,30 @@ namespace Vortex.Modules.Chats.Infrastructure.Persistence.Migrations
b.ToTable("Messages", "chats");
});
modelBuilder.Entity("Vortex.Modules.Chats.Domain.Reaction", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Emoji")
.IsRequired()
.HasColumnType("text");
b.Property<Guid>("MessageId")
.HasColumnType("uuid");
b.Property<Guid>("UserId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("MessageId", "UserId", "Emoji")
.IsUnique();
b.ToTable("MessageReactions", "chats");
});
modelBuilder.Entity("Vortex.Modules.Chats.Domain.ReadReceipt", b =>
{
b.Property<Guid>("Id")
@@ -131,6 +155,9 @@ namespace Vortex.Modules.Chats.Infrastructure.Persistence.Migrations
b1.Property<bool>("IsMuted")
.HasColumnType("boolean");
b1.Property<bool>("IsPinned")
.HasColumnType("boolean");
b1.Property<DateTime>("JoinedAt")
.HasColumnType("timestamp with time zone");
@@ -159,6 +186,9 @@ namespace Vortex.Modules.Chats.Infrastructure.Persistence.Migrations
{
b.OwnsMany("Vortex.Modules.Chats.Domain.Media", "Media", b1 =>
{
b1.Property<Guid>("MessageId")
.HasColumnType("uuid");
b1.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
@@ -166,9 +196,6 @@ namespace Vortex.Modules.Chats.Infrastructure.Persistence.Migrations
b1.Property<string>("Filename")
.HasColumnType("text");
b1.Property<Guid>("MessageId")
.HasColumnType("uuid");
b1.Property<long?>("Size")
.HasColumnType("bigint");
@@ -180,9 +207,7 @@ namespace Vortex.Modules.Chats.Infrastructure.Persistence.Migrations
.IsRequired()
.HasColumnType("text");
b1.HasKey("Id");
b1.HasIndex("MessageId");
b1.HasKey("MessageId", "Id");
b1.ToTable("MessageMedia", "chats");
@@ -190,36 +215,16 @@ namespace Vortex.Modules.Chats.Infrastructure.Persistence.Migrations
.HasForeignKey("MessageId");
});
b.OwnsMany("Vortex.Modules.Chats.Domain.Reaction", "Reactions", b1 =>
{
b1.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b1.Property<string>("Emoji")
.IsRequired()
.HasColumnType("text");
b1.Property<Guid>("MessageId")
.HasColumnType("uuid");
b1.Property<Guid>("UserId")
.HasColumnType("uuid");
b1.HasKey("Id");
b1.HasIndex("MessageId", "UserId", "Emoji")
.IsUnique();
b1.ToTable("MessageReactions", "chats");
b1.WithOwner()
.HasForeignKey("MessageId");
});
b.Navigation("Media");
});
b.Navigation("Reactions");
modelBuilder.Entity("Vortex.Modules.Chats.Domain.Reaction", b =>
{
b.HasOne("Vortex.Modules.Chats.Domain.Message", null)
.WithMany("Reactions")
.HasForeignKey("MessageId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Vortex.Modules.Chats.Domain.ReadReceipt", b =>
@@ -233,6 +238,8 @@ namespace Vortex.Modules.Chats.Infrastructure.Persistence.Migrations
modelBuilder.Entity("Vortex.Modules.Chats.Domain.Message", b =>
{
b.Navigation("Reactions");
b.Navigation("ReadBy");
});
#pragma warning restore 612, 618

View File

@@ -186,6 +186,28 @@ public sealed class ChatHub : Hub
}
}
// ────────────────────────────────────────────────────────────────
// Friend signals (Proxy methods for real-time notification)
// ────────────────────────────────────────────────────────────────
[HubMethodName("friend_request")]
public async Task FriendRequest(FriendSignalRequest request)
{
await SendToUserAsync(request.FriendId, "friend_request_received", new { userId = _userContext.UserId });
}
[HubMethodName("friend_accepted")]
public async Task FriendAccepted(FriendSignalRequest request)
{
await SendToUserAsync(request.FriendId, "friend_request_accepted", new { userId = _userContext.UserId });
}
[HubMethodName("friend_removed")]
public async Task FriendRemoved(FriendSignalRequest request)
{
await SendToUserAsync(request.FriendId, "friend_removed_notify", new { userId = _userContext.UserId });
}
// ────────────────────────────────────────────────────────────────
// WebRTC signaling
// ────────────────────────────────────────────────────────────────
@@ -470,4 +492,5 @@ public sealed class ChatHub : Hub
public record GroupIceCandidateRequest(string ChatId, string TargetUserId, object Candidate);
public record GroupRenegotiateRequest(string ChatId, string TargetUserId, object Offer);
public record GroupRenegotiateAnswerRequest(string ChatId, string TargetUserId, object Answer);
public record FriendSignalRequest(string FriendId);
}

View File

@@ -13,7 +13,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("Vortex.Modules.Chats")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+1ec6b438d29413482e1393c8ee5717f1211bd2c1")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+9daafae9c5956290dda1c718bb49ff72c77fafb8")]
[assembly: System.Reflection.AssemblyProductAttribute("Vortex.Modules.Chats")]
[assembly: System.Reflection.AssemblyTitleAttribute("Vortex.Modules.Chats")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]

View File

@@ -1 +1 @@
f9ffb401f31277f8b221e98682e16484fff43d34c6ed6bf8ad0e087ed2774d78
a2ba88a9d7ac278056acbcb427c11fabed9db58aac98411359499b8b1b34811c

View File

@@ -13,7 +13,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("Vortex.Modules.Identity")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+1ec6b438d29413482e1393c8ee5717f1211bd2c1")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+9daafae9c5956290dda1c718bb49ff72c77fafb8")]
[assembly: System.Reflection.AssemblyProductAttribute("Vortex.Modules.Identity")]
[assembly: System.Reflection.AssemblyTitleAttribute("Vortex.Modules.Identity")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]

View File

@@ -1 +1 @@
9d9ffd258fd1015aa25ef1b7c83a7e79e77e5f90a31d4742aa8a559cc7698555
2a9c951494821db92633ba9e6d674467f449af4a90ac9fed0ea49175b5b7917b