Мелкие правки
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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")]
|
||||
|
||||
@@ -1 +1 @@
|
||||
f9ffb401f31277f8b221e98682e16484fff43d34c6ed6bf8ad0e087ed2774d78
|
||||
a2ba88a9d7ac278056acbcb427c11fabed9db58aac98411359499b8b1b34811c
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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")]
|
||||
|
||||
@@ -1 +1 @@
|
||||
9d9ffd258fd1015aa25ef1b7c83a7e79e77e5f90a31d4742aa8a559cc7698555
|
||||
2a9c951494821db92633ba9e6d674467f449af4a90ac9fed0ea49175b5b7917b
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -13,7 +13,7 @@ using System.Reflection;
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("Vortex.Shared.Infrastructure")]
|
||||
[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.Shared.Infrastructure")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("Vortex.Shared.Infrastructure")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
@@ -1 +1 @@
|
||||
e0bbb37b072e496f3944a8671d38c2aa23b6d62d5e51acb6547971f52a1ef575
|
||||
745538ea2e4bf4b47bee0e7c23a71f34c85ffd4593e17f36ed6617330cf934ed
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -13,7 +13,7 @@ using System.Reflection;
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("Vortex.Shared.Kernel")]
|
||||
[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.Shared.Kernel")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("Vortex.Shared.Kernel")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
@@ -1 +1 @@
|
||||
b12266e8303643e0701f21b3366bc23363791cc5e63972b801eea2f6405398dc
|
||||
375a1236f22989deedeec81190d9f49c1e30becc8b5382dbeed7e3232f8c1b05
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -56,6 +56,7 @@ public sealed class ChatsController : ControllerBase
|
||||
id = m.Id,
|
||||
userId = m.UserId,
|
||||
role = m.Role,
|
||||
isPinned = m.IsPinned,
|
||||
user = user != null ? new {
|
||||
id = user.Id,
|
||||
username = user.Username,
|
||||
@@ -226,12 +227,19 @@ public sealed class ChatsController : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/pin")]
|
||||
public async Task<IActionResult> TogglePin(Guid id, [FromServices] IChatRepository chatRepository, CancellationToken ct)
|
||||
public async Task<IActionResult> TogglePin(Guid id, [FromServices] IChatRepository chatRepository, [FromServices] IChatsUnitOfWork uow, CancellationToken ct)
|
||||
{
|
||||
var chat = await chatRepository.GetByIdAsync(id, ct);
|
||||
if (chat == null || !chat.Members.Any(m => m.UserId == _userContext.UserId)) return NotFound();
|
||||
// Pin logic not stored in domain yet — return stub
|
||||
return Ok(new { isPinned = false });
|
||||
if (chat == null) return NotFound();
|
||||
|
||||
var member = chat.Members.FirstOrDefault(m => m.UserId == _userContext.UserId);
|
||||
if (member == null) return NotFound();
|
||||
|
||||
member.TogglePin();
|
||||
chatRepository.Update(chat);
|
||||
await uow.SaveChangesAsync(ct);
|
||||
|
||||
return Ok(new { isPinned = member.IsPinned });
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/members")]
|
||||
@@ -314,6 +322,7 @@ public sealed class ChatsController : ControllerBase
|
||||
id = m.Id,
|
||||
userId = m.UserId,
|
||||
role = m.Role,
|
||||
isPinned = m.IsPinned,
|
||||
user = user != null ? new {
|
||||
id = user.Id,
|
||||
username = user.Username,
|
||||
|
||||
@@ -118,33 +118,46 @@ public sealed class StoriesController : ControllerBase
|
||||
[HttpPost("{id}/view")]
|
||||
public async Task<IActionResult> ViewStory(Guid id, CancellationToken ct)
|
||||
{
|
||||
var story = await _context.Stories
|
||||
.Include(s => s.Viewers)
|
||||
.FirstOrDefaultAsync(s => s.Id == id, ct);
|
||||
|
||||
if (story == null) return NotFound();
|
||||
if (story.UserId == _userContext.UserId) return Ok(new { message = "Owner view" });
|
||||
|
||||
if (!story.Viewers.Any(v => v.UserId == _userContext.UserId))
|
||||
try
|
||||
{
|
||||
story.AddViewer(_userContext.UserId);
|
||||
await _context.SaveChangesAsync(ct);
|
||||
var story = await _context.Stories
|
||||
.Include(s => s.Viewers)
|
||||
.FirstOrDefaultAsync(s => s.Id == id, ct);
|
||||
|
||||
// Notify owner via SignalR
|
||||
var viewer = await _userRepository.GetByIdAsync(_userContext.UserId, ct);
|
||||
await _hubContext.Clients.User(story.UserId.ToString()).SendAsync("story_viewed", new
|
||||
if (story == null) return NotFound();
|
||||
if (story.UserId == _userContext.UserId) return Ok(new { message = "Owner view" });
|
||||
|
||||
if (!story.Viewers.Any(v => v.UserId == _userContext.UserId))
|
||||
{
|
||||
storyId = story.Id,
|
||||
userId = _userContext.UserId,
|
||||
username = viewer?.Username,
|
||||
displayName = viewer?.DisplayName,
|
||||
avatar = viewer?.Avatar,
|
||||
viewedAt = DateTime.UtcNow,
|
||||
viewCount = story.Viewers.Count
|
||||
}, ct);
|
||||
}
|
||||
story.AddViewer(_userContext.UserId);
|
||||
await _context.SaveChangesAsync(ct);
|
||||
|
||||
return Ok(new { message = "Story viewed" });
|
||||
// Notify owner via SignalR targetedly
|
||||
var viewer = await _userRepository.GetByIdAsync(_userContext.UserId, ct);
|
||||
await _hubContext.Clients.User(story.UserId.ToString()).SendAsync("story_viewed", new
|
||||
{
|
||||
storyId = story.Id,
|
||||
userId = _userContext.UserId,
|
||||
username = viewer?.Username,
|
||||
displayName = viewer?.DisplayName,
|
||||
avatar = viewer?.Avatar,
|
||||
viewedAt = DateTime.UtcNow,
|
||||
viewCount = story.Viewers.Count,
|
||||
ownerId = story.UserId
|
||||
}, ct);
|
||||
}
|
||||
|
||||
return Ok(new { message = "Story viewed" });
|
||||
}
|
||||
catch (DbUpdateException)
|
||||
{
|
||||
// Likely a unique constraint violation (already viewed)
|
||||
return Ok(new { message = "Story already viewed" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("{id}/viewers")]
|
||||
|
||||
@@ -12,6 +12,10 @@ using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using System.Security.Claims;
|
||||
|
||||
|
||||
|
||||
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
|
||||
|
||||
@@ -83,6 +87,8 @@ builder.Services.AddSignalR()
|
||||
options.PayloadSerializerOptions.Converters.Add(new JsonStringEnumConverter());
|
||||
});
|
||||
|
||||
builder.Services.AddSingleton<IUserIdProvider, CustomUserIdProvider>();
|
||||
|
||||
// Настройка JWT Аутентификации
|
||||
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||
.AddJwtBearer(options =>
|
||||
@@ -163,3 +169,11 @@ app.MapControllers();
|
||||
app.MapHub<ChatHub>("/hubs/chat");
|
||||
|
||||
app.Run();
|
||||
|
||||
public class CustomUserIdProvider : IUserIdProvider
|
||||
{
|
||||
public string? GetUserId(HubConnectionContext connection)
|
||||
{
|
||||
return connection.User?.FindFirstValue("sub") ?? connection.User?.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -13,7 +13,7 @@ using System.Reflection;
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("Vortex.Host")]
|
||||
[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.Host")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("Vortex.Host")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
@@ -1 +1 @@
|
||||
c8308646f5647671185661b28c5c3eabe4dd11328d5c7402bbee505991c08d6d
|
||||
2503d90bfacb43b23a8199c8a1cb6d660f9b8abff8c009ccb8b71721eacb6d50
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
||||
{"GlobalPropertiesHash":"2XkPbXFkjVRjCUBTJbQq8CDd7pjsUOS1j3gPHS73xDA=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["uOUIBvk7jdHp7c93RIuECwNykpOmiZnVmQxETsngeR0=","ToV71GGCtcAptkaGqQYXeNtDQnAJEljvYAkAKnIRZ\u002Bs=","2fjU\u002BjCBiHEuKj2uQgoWti0oZXkpAdICFty/O9cJSyc=","aTLUi985DoL1wb7QmuhiHoO7J3hi30WEVAUbYVkXxss=","HLBqD\u002BLr6lExIdmRzMVhSCOgnqBnWCEhelB/vqt5YM0="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||
{"GlobalPropertiesHash":"2XkPbXFkjVRjCUBTJbQq8CDd7pjsUOS1j3gPHS73xDA=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["uOUIBvk7jdHp7c93RIuECwNykpOmiZnVmQxETsngeR0=","ToV71GGCtcAptkaGqQYXeNtDQnAJEljvYAkAKnIRZ\u002Bs=","2fjU\u002BjCBiHEuKj2uQgoWti0oZXkpAdICFty/O9cJSyc=","aTLUi985DoL1wb7QmuhiHoO7J3hi30WEVAUbYVkXxss=","6IHvDihzP7nz2jPiGaen/UwCBL1mH\u002BfY\u002BnUJBq/hoXU="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||
@@ -1 +1 @@
|
||||
{"GlobalPropertiesHash":"2L4HBRt/ChGO9rohi3wtSDqo95X47fme5m8OHgJIZL0=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["uOUIBvk7jdHp7c93RIuECwNykpOmiZnVmQxETsngeR0=","ToV71GGCtcAptkaGqQYXeNtDQnAJEljvYAkAKnIRZ\u002Bs=","2fjU\u002BjCBiHEuKj2uQgoWti0oZXkpAdICFty/O9cJSyc=","aTLUi985DoL1wb7QmuhiHoO7J3hi30WEVAUbYVkXxss=","HLBqD\u002BLr6lExIdmRzMVhSCOgnqBnWCEhelB/vqt5YM0="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||
{"GlobalPropertiesHash":"2L4HBRt/ChGO9rohi3wtSDqo95X47fme5m8OHgJIZL0=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["uOUIBvk7jdHp7c93RIuECwNykpOmiZnVmQxETsngeR0=","ToV71GGCtcAptkaGqQYXeNtDQnAJEljvYAkAKnIRZ\u002Bs=","2fjU\u002BjCBiHEuKj2uQgoWti0oZXkpAdICFty/O9cJSyc=","aTLUi985DoL1wb7QmuhiHoO7J3hi30WEVAUbYVkXxss=","6IHvDihzP7nz2jPiGaen/UwCBL1mH\u002BfY\u002BnUJBq/hoXU="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 147 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
Reference in New Issue
Block a user