Новый дизайн, удаление
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Vortex.Modules.Chats.Domain;
|
||||
using Vortex.Modules.Chats.Infrastructure.SignalR;
|
||||
using Vortex.Shared.Kernel;
|
||||
|
||||
namespace Vortex.Modules.Chats.Application.Messages.Delete;
|
||||
|
||||
public sealed record DeleteMessagesCommand(
|
||||
Guid ChatId,
|
||||
Guid UserId,
|
||||
List<Guid> MessageIds,
|
||||
bool DeleteForAll) : global::Vortex.Shared.Kernel.ICommand;
|
||||
|
||||
public sealed class DeleteMessagesCommandHandler : global::Vortex.Shared.Kernel.ICommandHandler<DeleteMessagesCommand>
|
||||
{
|
||||
private readonly IMessageRepository _messageRepository;
|
||||
private readonly Vortex.Modules.Chats.Application.Abstractions.IChatsUnitOfWork _unitOfWork;
|
||||
private readonly IHubContext<ChatHub> _hubContext;
|
||||
|
||||
public DeleteMessagesCommandHandler(
|
||||
IMessageRepository messageRepository,
|
||||
Vortex.Modules.Chats.Application.Abstractions.IChatsUnitOfWork unitOfWork,
|
||||
IHubContext<ChatHub> hubContext)
|
||||
{
|
||||
_messageRepository = messageRepository;
|
||||
_unitOfWork = unitOfWork;
|
||||
_hubContext = hubContext;
|
||||
}
|
||||
|
||||
public async Task<global::Vortex.Shared.Kernel.Result> Handle(DeleteMessagesCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var id in request.MessageIds)
|
||||
{
|
||||
var message = await _messageRepository.GetByIdAsync(id, cancellationToken);
|
||||
if (message is null || message.ChatId != request.ChatId) continue;
|
||||
|
||||
if (request.DeleteForAll)
|
||||
{
|
||||
if (message.SenderId == request.UserId)
|
||||
{
|
||||
message.Delete();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
message.DeleteForUser(request.UserId);
|
||||
}
|
||||
}
|
||||
|
||||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||||
|
||||
if (request.DeleteForAll)
|
||||
{
|
||||
await _hubContext.Clients.Group(request.ChatId.ToString()).SendAsync("messages_deleted", new
|
||||
{
|
||||
chatId = request.ChatId,
|
||||
messageIds = request.MessageIds,
|
||||
deleteForAll = true
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Уведомляем только самого пользователя (все его текущие сессии)
|
||||
await _hubContext.Clients.User(request.UserId.ToString()).SendAsync("messages_deleted", new
|
||||
{
|
||||
chatId = request.ChatId,
|
||||
messageIds = request.MessageIds,
|
||||
deleteForAll = false
|
||||
});
|
||||
}
|
||||
|
||||
return global::Vortex.Shared.Kernel.Result.Success();
|
||||
}
|
||||
}
|
||||
@@ -34,24 +34,31 @@ public sealed class AddReactionCommandHandler : ICommandHandler<AddReactionComma
|
||||
|
||||
public async Task<Result> Handle(AddReactionCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var message = await _messageRepository.GetByIdAsync(request.MessageId, cancellationToken);
|
||||
if (message is null) return Result.Failure(new Error("Messages.NotFound", "Message not found."));
|
||||
|
||||
message.AddReaction(request.UserId, request.Emoji);
|
||||
|
||||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||||
|
||||
var username = await _displayNameProvider.GetDisplayNameAsync(request.UserId, cancellationToken);
|
||||
|
||||
await _hubContext.Clients.Group(request.ChatId.ToString()).SendAsync("reaction_added", new
|
||||
try
|
||||
{
|
||||
messageId = request.MessageId,
|
||||
chatId = request.ChatId,
|
||||
userId = request.UserId,
|
||||
username = username,
|
||||
emoji = request.Emoji
|
||||
});
|
||||
var message = await _messageRepository.GetByIdAsync(request.MessageId, cancellationToken);
|
||||
if (message is null) return Result.Failure(new Error("Messages.NotFound", "Message not found."));
|
||||
|
||||
return Result.Success();
|
||||
message.AddReaction(request.UserId, request.Emoji);
|
||||
|
||||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||||
|
||||
var username = await _displayNameProvider.GetDisplayNameAsync(request.UserId, cancellationToken);
|
||||
|
||||
await _hubContext.Clients.Group(request.ChatId.ToString()).SendAsync("reaction_added", new
|
||||
{
|
||||
messageId = request.MessageId,
|
||||
chatId = request.ChatId,
|
||||
userId = request.UserId,
|
||||
username = username,
|
||||
emoji = request.Emoji
|
||||
});
|
||||
|
||||
return Result.Success();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result.Failure(new Error("AddReaction.Error", ex.Message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,21 +31,28 @@ public sealed class RemoveReactionCommandHandler : ICommandHandler<RemoveReactio
|
||||
|
||||
public async Task<Result> Handle(RemoveReactionCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var message = await _messageRepository.GetByIdAsync(request.MessageId, cancellationToken);
|
||||
if (message is null) return Result.Failure(new Error("Messages.NotFound", "Message not found."));
|
||||
|
||||
message.RemoveReaction(request.UserId, request.Emoji);
|
||||
|
||||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||||
|
||||
await _hubContext.Clients.Group(request.ChatId.ToString()).SendAsync("reaction_removed", new
|
||||
try
|
||||
{
|
||||
messageId = request.MessageId,
|
||||
chatId = request.ChatId,
|
||||
userId = request.UserId,
|
||||
emoji = request.Emoji
|
||||
});
|
||||
var message = await _messageRepository.GetByIdAsync(request.MessageId, cancellationToken);
|
||||
if (message is null) return Result.Failure(new Error("Messages.NotFound", "Message not found."));
|
||||
|
||||
return Result.Success();
|
||||
message.RemoveReaction(request.UserId, request.Emoji);
|
||||
|
||||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||||
|
||||
await _hubContext.Clients.Group(request.ChatId.ToString()).SendAsync("reaction_removed", new
|
||||
{
|
||||
messageId = request.MessageId,
|
||||
chatId = request.ChatId,
|
||||
userId = request.UserId,
|
||||
emoji = request.Emoji
|
||||
});
|
||||
|
||||
return Result.Success();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result.Failure(new Error("RemoveReaction.Error", ex.Message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@ using Vortex.Shared.Kernel;
|
||||
|
||||
namespace Vortex.Modules.Chats.Domain;
|
||||
|
||||
public sealed record ChatCreatedDomainEvent(Chat Chat) : IDomainEvent;
|
||||
public sealed record ChatMemberAddedDomainEvent(Guid ChatId, Guid UserId) : IDomainEvent;
|
||||
|
||||
/// <summary>
|
||||
/// Тип чата: личный или групповой.
|
||||
/// </summary>
|
||||
@@ -48,7 +51,9 @@ public sealed class Chat : AggregateRoot<Guid>
|
||||
/// </summary>
|
||||
public static Chat CreatePersonal()
|
||||
{
|
||||
return new Chat(Guid.NewGuid(), ChatType.Personal, null, null);
|
||||
var chat = new Chat(Guid.NewGuid(), ChatType.Personal, null, null);
|
||||
chat.RaiseDomainEvent(new ChatCreatedDomainEvent(chat));
|
||||
return chat;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -56,7 +61,9 @@ public sealed class Chat : AggregateRoot<Guid>
|
||||
/// </summary>
|
||||
public static Chat CreateGroup(string name, string? avatar = null)
|
||||
{
|
||||
return new Chat(Guid.NewGuid(), ChatType.Group, name, avatar);
|
||||
var chat = new Chat(Guid.NewGuid(), ChatType.Group, name, avatar);
|
||||
chat.RaiseDomainEvent(new ChatCreatedDomainEvent(chat));
|
||||
return chat;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -64,13 +71,16 @@ public sealed class Chat : AggregateRoot<Guid>
|
||||
/// </summary>
|
||||
public static Chat Create(string? name, ChatType type, string? avatar = null)
|
||||
{
|
||||
return new Chat(Guid.NewGuid(), type, name, avatar);
|
||||
var chat = new Chat(Guid.NewGuid(), type, name, avatar);
|
||||
chat.RaiseDomainEvent(new ChatCreatedDomainEvent(chat));
|
||||
return chat;
|
||||
}
|
||||
|
||||
public void AddMember(Guid userId, string role = "member")
|
||||
{
|
||||
if (_members.Any(m => m.UserId == userId)) return;
|
||||
_members.Add(new ChatMember(Id, userId, role));
|
||||
RaiseDomainEvent(new ChatMemberAddedDomainEvent(Id, userId));
|
||||
}
|
||||
|
||||
public void RemoveMember(Guid userId)
|
||||
|
||||
@@ -29,6 +29,9 @@ public sealed class Message : AggregateRoot<Guid>
|
||||
private readonly List<ReadReceipt> _readBy = new();
|
||||
public IReadOnlyCollection<ReadReceipt> ReadBy => _readBy.AsReadOnly();
|
||||
|
||||
private readonly List<Guid> _deletedByUsers = new();
|
||||
public IReadOnlyCollection<Guid> DeletedByUsers => _deletedByUsers.AsReadOnly();
|
||||
|
||||
private Message(Guid id, Guid chatId, Guid senderId, string? content, string type, Guid? replyToId = null, string? quote = null, Guid? forwardedFromId = null) : base(id)
|
||||
{
|
||||
ChatId = chatId;
|
||||
@@ -84,6 +87,16 @@ public sealed class Message : AggregateRoot<Guid>
|
||||
{
|
||||
Content = null;
|
||||
IsDeleted = true;
|
||||
_media.Clear();
|
||||
_reactions.Clear();
|
||||
}
|
||||
|
||||
public void DeleteForUser(Guid userId)
|
||||
{
|
||||
if (!_deletedByUsers.Contains(userId))
|
||||
{
|
||||
_deletedByUsers.Add(userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
@@ -101,6 +114,11 @@ public sealed class Reaction : Entity<Guid>
|
||||
UserId = userId;
|
||||
Emoji = emoji;
|
||||
}
|
||||
|
||||
private Reaction() : base(Guid.Empty)
|
||||
{
|
||||
Emoji = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -122,4 +140,10 @@ public sealed class Media : Entity<Guid>
|
||||
Filename = filename;
|
||||
Size = size;
|
||||
}
|
||||
|
||||
private Media() : base(Guid.Empty)
|
||||
{
|
||||
Type = string.Empty;
|
||||
Url = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Vortex.Modules.Chats.Domain;
|
||||
using Vortex.Modules.Chats.Infrastructure.SignalR;
|
||||
using Vortex.Shared.Kernel;
|
||||
|
||||
namespace Vortex.Modules.Chats.Infrastructure.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// Обработчик доменного события создания чата.
|
||||
/// </summary>
|
||||
public sealed class ChatCreatedDomainEventHandler : INotificationHandler<ChatCreatedDomainEvent>, INotificationHandler<ChatMemberAddedDomainEvent>
|
||||
{
|
||||
private readonly IHubContext<ChatHub> _hubContext;
|
||||
private readonly IChatRepository _chatRepository;
|
||||
private readonly IUserDisplayNameProvider _displayNameProvider;
|
||||
|
||||
public ChatCreatedDomainEventHandler(
|
||||
IHubContext<ChatHub> hubContext,
|
||||
IChatRepository chatRepository,
|
||||
IUserDisplayNameProvider displayNameProvider)
|
||||
{
|
||||
_hubContext = hubContext;
|
||||
_chatRepository = chatRepository;
|
||||
_displayNameProvider = displayNameProvider;
|
||||
}
|
||||
|
||||
public async Task Handle(ChatCreatedDomainEvent notification, CancellationToken cancellationToken)
|
||||
{
|
||||
await NotifyNewChatAsync(notification.Chat, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task Handle(ChatMemberAddedDomainEvent notification, CancellationToken cancellationToken)
|
||||
{
|
||||
var chat = await _chatRepository.GetByIdAsync(notification.ChatId, cancellationToken);
|
||||
if (chat != null)
|
||||
{
|
||||
// Уведомляем всех участников, что кто-то добавлен (или пользователь сам видит новый чат)
|
||||
await NotifyNewChatAsync(chat, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task NotifyNewChatAsync(Chat chat, CancellationToken cancellationToken, string? targetUserId = null)
|
||||
{
|
||||
var members = new List<object>();
|
||||
foreach (var member in chat.Members)
|
||||
{
|
||||
var userInfo = await _displayNameProvider.GetUserInfoAsync(member.UserId, cancellationToken);
|
||||
members.Add(new
|
||||
{
|
||||
member.Id,
|
||||
member.UserId,
|
||||
member.Role,
|
||||
member.JoinedAt,
|
||||
User = userInfo != null
|
||||
? new { userInfo.Id, userInfo.Username, userInfo.DisplayName, userInfo.Avatar, isOnline = false }
|
||||
: new { Id = member.UserId, Username = "unknown", DisplayName = "Unknown", Avatar = (string?)null, isOnline = false }
|
||||
});
|
||||
}
|
||||
|
||||
var payload = new
|
||||
{
|
||||
chat.Id,
|
||||
chat.Type,
|
||||
chat.Name,
|
||||
chat.Avatar,
|
||||
chat.CreatedAt,
|
||||
Members = members,
|
||||
Messages = new List<object>(),
|
||||
UnreadCount = 0
|
||||
};
|
||||
|
||||
if (targetUserId != null)
|
||||
{
|
||||
// Отправляем конкретному пользователю (по всем его соединениям)
|
||||
// Мы можем использовать метод Hub или просто отправить в группу UserId (если она есть)
|
||||
// В нашем ChatHub мы добавляем пользователя в группы чатов при подключении.
|
||||
// Но для нового чата он еще не в группе.
|
||||
// Поэтому отправляем через User ID (SignalR поддерживает Clients.User(id))
|
||||
await _hubContext.Clients.User(targetUserId).SendAsync("new_chat", payload, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Уведомляем всех текущих участников
|
||||
foreach (var member in chat.Members)
|
||||
{
|
||||
await _hubContext.Clients.User(member.UserId.ToString()).SendAsync("new_chat", payload, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||
using MediatR;
|
||||
using Vortex.Modules.Chats.Domain;
|
||||
using Vortex.Shared.Kernel;
|
||||
@@ -23,6 +24,12 @@ public sealed class ChatsDbContext : DbContext, IChatsUnitOfWork
|
||||
public DbSet<Chat> Chats => Set<Chat>();
|
||||
public DbSet<Message> Messages => Set<Message>();
|
||||
public DbSet<ReadReceipt> ReadReceipts => Set<ReadReceipt>();
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
base.OnConfiguring(optionsBuilder);
|
||||
optionsBuilder.ConfigureWarnings(w => w.Ignore(RelationalEventId.PendingModelChangesWarning));
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
@@ -40,7 +47,7 @@ public sealed class ChatsDbContext : DbContext, IChatsUnitOfWork
|
||||
mb.HasKey(m => m.Id);
|
||||
mb.WithOwner().HasForeignKey(m => m.ChatId);
|
||||
mb.HasIndex(m => new { m.ChatId, m.UserId }).IsUnique();
|
||||
});
|
||||
}).Navigation(c => c.Members).UsePropertyAccessMode(PropertyAccessMode.Field);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Message>(builder =>
|
||||
@@ -51,17 +58,26 @@ public sealed class ChatsDbContext : DbContext, IChatsUnitOfWork
|
||||
builder.OwnsMany(m => m.Media, mb =>
|
||||
{
|
||||
mb.ToTable("MessageMedia");
|
||||
mb.HasKey(x => x.Id);
|
||||
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.WithOwner().HasForeignKey(x => x.MessageId);
|
||||
mb.HasIndex(x => new { x.MessageId, x.UserId, x.Emoji }).IsUnique();
|
||||
});
|
||||
}).Navigation(m => m.Reactions).UsePropertyAccessMode(PropertyAccessMode.Field);
|
||||
|
||||
builder.PrimitiveCollection(m => m.DeletedByUsers)
|
||||
.HasColumnName("DeletedByUsers")
|
||||
.UsePropertyAccessMode(PropertyAccessMode.Field);
|
||||
|
||||
builder.HasMany(m => m.ReadBy)
|
||||
.WithOne()
|
||||
.HasForeignKey(r => r.MessageId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.Navigation(m => m.ReadBy).UsePropertyAccessMode(PropertyAccessMode.Field);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<ReadReceipt>(builder =>
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
// <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("20260312174910_UpdateChatsSchema")]
|
||||
partial class UpdateChatsSchema
|
||||
{
|
||||
/// <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.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<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>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("Filename")
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<Guid>("MessageId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<long?>("Size")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b1.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("Url")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.HasKey("Id");
|
||||
|
||||
b1.HasIndex("MessageId");
|
||||
|
||||
b1.ToTable("MessageMedia", "chats");
|
||||
|
||||
b1.WithOwner()
|
||||
.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.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("ReadBy");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Vortex.Modules.Chats.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class UpdateChatsSchema : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<Guid[]>(
|
||||
name: "DeletedByUsers",
|
||||
schema: "chats",
|
||||
table: "Messages",
|
||||
type: "uuid[]",
|
||||
nullable: false,
|
||||
defaultValue: new Guid[0]);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "DeletedByUsers",
|
||||
schema: "chats",
|
||||
table: "Messages");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,6 +62,11 @@ namespace Vortex.Modules.Chats.Infrastructure.Persistence.Migrations
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.PrimitiveCollection<Guid[]>("DeletedByUsers")
|
||||
.IsRequired()
|
||||
.HasColumnType("uuid[]")
|
||||
.HasColumnName("DeletedByUsers");
|
||||
|
||||
b.Property<Guid?>("ForwardedFromId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ public sealed class ChatHub : Hub
|
||||
{
|
||||
// Маппинг userId → список connectionId
|
||||
private static readonly ConcurrentDictionary<string, HashSet<string>> _userConnections = new();
|
||||
// chatId → (userId → ParticipantInfo)
|
||||
private static readonly ConcurrentDictionary<string, ConcurrentDictionary<string, ParticipantInfo>> _groupCallParticipants = new();
|
||||
|
||||
private readonly ISender _sender;
|
||||
private readonly IUserContext _userContext;
|
||||
@@ -118,6 +120,22 @@ public sealed class ChatHub : Hub
|
||||
});
|
||||
}
|
||||
|
||||
[HubMethodName("delete_messages")]
|
||||
public async Task DeleteMessages(DeleteMessagesHubRequest request)
|
||||
{
|
||||
var parsedIds = request.MessageIds
|
||||
.Select(id => Guid.TryParse(id, out var parsed) ? parsed : Guid.Empty)
|
||||
.Where(id => id != Guid.Empty)
|
||||
.ToList();
|
||||
|
||||
if (parsedIds.Any())
|
||||
{
|
||||
var command = new Vortex.Modules.Chats.Application.Messages.Delete.DeleteMessagesCommand(
|
||||
request.ChatId, _userContext.UserId, parsedIds, request.DeleteForAll);
|
||||
await _sender.Send(command);
|
||||
}
|
||||
}
|
||||
|
||||
[HubMethodName("typing_start")]
|
||||
public async Task TypingStart(string chatId)
|
||||
{
|
||||
@@ -130,12 +148,30 @@ public sealed class ChatHub : Hub
|
||||
await Clients.Group(chatId).SendAsync("user_stopped_typing", new { ChatId = chatId, UserId = _userContext.UserId });
|
||||
}
|
||||
|
||||
[HubMethodName("join_chat")]
|
||||
public async Task JoinChat(string chatId)
|
||||
{
|
||||
// Simple security check: check if user is member of chat (optional but recommended)
|
||||
if (Guid.TryParse(chatId, out var chatGuid))
|
||||
{
|
||||
var userChats = await _chatRepository.GetUserChatsAsync(_userContext.UserId, Context.ConnectionAborted);
|
||||
if (userChats.Any(c => c.Id == chatGuid))
|
||||
{
|
||||
await Groups.AddToGroupAsync(Context.ConnectionId, chatId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HubMethodName("add_reaction")]
|
||||
public async Task AddReaction(AddReactionRequest request)
|
||||
{
|
||||
var command = new Vortex.Modules.Chats.Application.Messages.React.AddReactionCommand(
|
||||
request.MessageId, _userContext.UserId, request.Emoji, request.ChatId);
|
||||
await _sender.Send(command);
|
||||
var result = await _sender.Send(command);
|
||||
if (result.IsFailure)
|
||||
{
|
||||
throw new HubException(result.Error.Description);
|
||||
}
|
||||
}
|
||||
|
||||
[HubMethodName("remove_reaction")]
|
||||
@@ -143,7 +179,11 @@ public sealed class ChatHub : Hub
|
||||
{
|
||||
var command = new Vortex.Modules.Chats.Application.Messages.React.RemoveReactionCommand(
|
||||
request.MessageId, _userContext.UserId, request.Emoji, request.ChatId);
|
||||
await _sender.Send(command);
|
||||
var result = await _sender.Send(command);
|
||||
if (result.IsFailure)
|
||||
{
|
||||
throw new HubException(result.Error.Description);
|
||||
}
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
@@ -255,6 +295,150 @@ public sealed class ChatHub : Hub
|
||||
});
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// Group Call signals
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
|
||||
[HubMethodName("group_call_join")]
|
||||
public async Task GroupCallJoin(GroupCallJoinRequest request)
|
||||
{
|
||||
var chatId = request.ChatId;
|
||||
var userId = _userContext.UserId.ToString();
|
||||
|
||||
var displayName = Context.User?.FindFirstValue("name") ?? Context.User?.FindFirstValue(ClaimTypes.Name) ?? "User";
|
||||
var avatar = Context.User?.FindFirstValue("avatar");
|
||||
var username = Context.User?.FindFirstValue("unique_name") ?? Context.User?.Identity?.Name ?? "user";
|
||||
|
||||
var userInfo = new ParticipantInfo(userId, username, displayName, avatar);
|
||||
|
||||
var participants = _groupCallParticipants.GetOrAdd(chatId, _ => new ConcurrentDictionary<string, ParticipantInfo>());
|
||||
participants.TryAdd(userId, userInfo);
|
||||
|
||||
// Notify others
|
||||
await Clients.Group(chatId).SendAsync("group_call_user_joined", new {
|
||||
chatId = chatId,
|
||||
userId = userId,
|
||||
userInfo = userInfo
|
||||
});
|
||||
|
||||
// Send current participants to joiner (excluding self)
|
||||
var others = participants.Values.Where(p => p.Id != userId).ToList();
|
||||
await Clients.Caller.SendAsync("group_call_participants", new {
|
||||
chatId = chatId,
|
||||
participants = others
|
||||
});
|
||||
}
|
||||
|
||||
[HubMethodName("group_call_leave")]
|
||||
public async Task GroupCallLeave(string chatId)
|
||||
{
|
||||
var userId = _userContext.UserId.ToString();
|
||||
if (_groupCallParticipants.TryGetValue(chatId, out var participants))
|
||||
{
|
||||
participants.TryRemove(userId, out _);
|
||||
if (participants.IsEmpty)
|
||||
{
|
||||
_groupCallParticipants.TryRemove(chatId, out _);
|
||||
}
|
||||
}
|
||||
|
||||
await Clients.Group(chatId).SendAsync("group_call_user_left", new {
|
||||
chatId = chatId,
|
||||
userId = userId
|
||||
});
|
||||
}
|
||||
|
||||
[HubMethodName("group_call_offer")]
|
||||
public async Task GroupCallOffer(GroupCallOfferRequest request)
|
||||
{
|
||||
await SendToUserAsync(request.TargetUserId, "group_call_offer", new
|
||||
{
|
||||
chatId = request.ChatId,
|
||||
from = _userContext.UserId.ToString(),
|
||||
offer = request.Offer
|
||||
});
|
||||
}
|
||||
|
||||
[HubMethodName("group_call_answer")]
|
||||
public async Task GroupCallAnswer(GroupCallAnswerRequest request)
|
||||
{
|
||||
await SendToUserAsync(request.TargetUserId, "group_call_answer", new
|
||||
{
|
||||
chatId = request.ChatId,
|
||||
from = _userContext.UserId.ToString(),
|
||||
answer = request.Answer
|
||||
});
|
||||
}
|
||||
|
||||
[HubMethodName("group_ice_candidate")]
|
||||
public async Task GroupIceCandidate(GroupIceCandidateRequest request)
|
||||
{
|
||||
await SendToUserAsync(request.TargetUserId, "group_ice_candidate", new
|
||||
{
|
||||
chatId = request.ChatId,
|
||||
from = _userContext.UserId.ToString(),
|
||||
candidate = request.Candidate
|
||||
});
|
||||
}
|
||||
|
||||
[HubMethodName("group_call_renegotiate")]
|
||||
public async Task GroupRenegotiate(GroupRenegotiateRequest request)
|
||||
{
|
||||
await SendToUserAsync(request.TargetUserId, "group_call_renegotiate", new
|
||||
{
|
||||
chatId = request.ChatId,
|
||||
from = _userContext.UserId.ToString(),
|
||||
offer = request.Offer
|
||||
});
|
||||
}
|
||||
|
||||
[HubMethodName("group_call_renegotiate_answer")]
|
||||
public async Task GroupRenegotiateAnswer(GroupRenegotiateAnswerRequest request)
|
||||
{
|
||||
await SendToUserAsync(request.TargetUserId, "group_call_renegotiate_answer", new
|
||||
{
|
||||
chatId = request.ChatId,
|
||||
from = _userContext.UserId.ToString(),
|
||||
answer = request.Answer
|
||||
});
|
||||
}
|
||||
|
||||
[HubMethodName("screen_share_started")]
|
||||
public async Task ScreenShareStarted(string chatId)
|
||||
{
|
||||
var userId = _userContext.UserId.ToString();
|
||||
if (_groupCallParticipants.TryGetValue(chatId, out var participants))
|
||||
{
|
||||
if (participants.TryGetValue(userId, out var info))
|
||||
{
|
||||
participants[userId] = info with { IsSharingScreen = true };
|
||||
}
|
||||
}
|
||||
|
||||
await Clients.Group(chatId).SendAsync("screen_share_started", new {
|
||||
chatId = chatId,
|
||||
userId = userId
|
||||
});
|
||||
}
|
||||
|
||||
[HubMethodName("screen_share_stopped")]
|
||||
public async Task ScreenShareStopped(string chatId)
|
||||
{
|
||||
var userId = _userContext.UserId.ToString();
|
||||
if (_groupCallParticipants.TryGetValue(chatId, out var participants))
|
||||
{
|
||||
if (participants.TryGetValue(userId, out var info))
|
||||
{
|
||||
participants[userId] = info with { IsSharingScreen = false };
|
||||
}
|
||||
}
|
||||
|
||||
await Clients.Group(chatId).SendAsync("screen_share_stopped", new {
|
||||
chatId = chatId,
|
||||
userId = userId
|
||||
});
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// Records
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
@@ -278,4 +462,12 @@ public sealed class ChatHub : Hub
|
||||
public record CallTypeChangedRequest(string TargetUserId, string CallType);
|
||||
public record AddReactionRequest(Guid MessageId, Guid ChatId, string Emoji);
|
||||
public record RemoveReactionRequest(Guid MessageId, Guid ChatId, string Emoji);
|
||||
public record DeleteMessagesHubRequest(Guid ChatId, List<string> MessageIds, bool DeleteForAll);
|
||||
public record GroupCallJoinRequest(string ChatId, string CallType);
|
||||
public record ParticipantInfo(string Id, string Username, string DisplayName, string? Avatar, bool IsSharingScreen = false);
|
||||
public record GroupCallOfferRequest(string ChatId, string TargetUserId, object Offer);
|
||||
public record GroupCallAnswerRequest(string ChatId, string TargetUserId, object Answer);
|
||||
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);
|
||||
}
|
||||
|
||||
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+d025c98e61cc08a53e6ebc62544d35be73a039e7")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+1ec6b438d29413482e1393c8ee5717f1211bd2c1")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("Vortex.Modules.Chats")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("Vortex.Modules.Chats")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
@@ -1 +1 @@
|
||||
3f81474107c936fb7023dd1f1d41b8c6e04cc78e956d41fb0e98d29b21aae2c3
|
||||
f9ffb401f31277f8b221e98682e16484fff43d34c6ed6bf8ad0e087ed2774d78
|
||||
|
||||
@@ -10,7 +10,7 @@ build_property.PlatformNeutralAssembly =
|
||||
build_property.EnforceExtendedAnalyzerRules =
|
||||
build_property._SupportedPlatformList = Linux,macOS,Windows
|
||||
build_property.RootNamespace = Vortex.Modules.Chats
|
||||
build_property.ProjectDir = e:\GIT\forkmessager\apps\server-net\src\Modules\Chats\
|
||||
build_property.ProjectDir = E:\GIT\forkmessager\apps\server-net\src\Modules\Chats\
|
||||
build_property.EnableComHosting =
|
||||
build_property.EnableGeneratedComInterfaceComImportInterop =
|
||||
build_property.EffectiveAnalysisLevelStyle = 10.0
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
||||
361b9f18466c016bfc9d27e7847a8f24463479b03b82ec7866a1124dede24edb
|
||||
b343bc938c4e02003d18f33f285c39da0e0ac9c0a7417a27b8b695b2e991a45e
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,17 +1,17 @@
|
||||
{
|
||||
"format": 1,
|
||||
"restore": {
|
||||
"e:\\GIT\\forkmessager\\apps\\server-net\\src\\Modules\\Chats\\Vortex.Modules.Chats.csproj": {}
|
||||
"E:\\GIT\\forkmessager\\apps\\server-net\\src\\Modules\\Chats\\Vortex.Modules.Chats.csproj": {}
|
||||
},
|
||||
"projects": {
|
||||
"e:\\GIT\\forkmessager\\apps\\server-net\\src\\Modules\\Chats\\Vortex.Modules.Chats.csproj": {
|
||||
"E:\\GIT\\forkmessager\\apps\\server-net\\src\\Modules\\Chats\\Vortex.Modules.Chats.csproj": {
|
||||
"version": "1.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Modules\\Chats\\Vortex.Modules.Chats.csproj",
|
||||
"projectUniqueName": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Modules\\Chats\\Vortex.Modules.Chats.csproj",
|
||||
"projectName": "Vortex.Modules.Chats",
|
||||
"projectPath": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Modules\\Chats\\Vortex.Modules.Chats.csproj",
|
||||
"projectPath": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Modules\\Chats\\Vortex.Modules.Chats.csproj",
|
||||
"packagesPath": "C:\\Users\\HomePC\\.nuget\\packages\\",
|
||||
"outputPath": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Modules\\Chats\\obj\\",
|
||||
"outputPath": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Modules\\Chats\\obj\\",
|
||||
"projectStyle": "PackageReference",
|
||||
"fallbackFolders": [
|
||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
||||
@@ -33,8 +33,8 @@
|
||||
"net10.0": {
|
||||
"targetAlias": "net10.0",
|
||||
"projectReferences": {
|
||||
"e:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj": {
|
||||
"projectPath": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj"
|
||||
"E:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj": {
|
||||
"projectPath": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -518,14 +518,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"e:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj": {
|
||||
"E:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj": {
|
||||
"version": "1.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj",
|
||||
"projectUniqueName": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj",
|
||||
"projectName": "Vortex.Shared.Kernel",
|
||||
"projectPath": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj",
|
||||
"projectPath": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj",
|
||||
"packagesPath": "C:\\Users\\HomePC\\.nuget\\packages\\",
|
||||
"outputPath": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\obj\\",
|
||||
"outputPath": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\obj\\",
|
||||
"projectStyle": "PackageReference",
|
||||
"fallbackFolders": [
|
||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
||||
|
||||
@@ -785,11 +785,11 @@
|
||||
"project": {
|
||||
"version": "1.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Modules\\Chats\\Vortex.Modules.Chats.csproj",
|
||||
"projectUniqueName": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Modules\\Chats\\Vortex.Modules.Chats.csproj",
|
||||
"projectName": "Vortex.Modules.Chats",
|
||||
"projectPath": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Modules\\Chats\\Vortex.Modules.Chats.csproj",
|
||||
"projectPath": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Modules\\Chats\\Vortex.Modules.Chats.csproj",
|
||||
"packagesPath": "C:\\Users\\HomePC\\.nuget\\packages\\",
|
||||
"outputPath": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Modules\\Chats\\obj\\",
|
||||
"outputPath": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Modules\\Chats\\obj\\",
|
||||
"projectStyle": "PackageReference",
|
||||
"fallbackFolders": [
|
||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
||||
@@ -811,8 +811,8 @@
|
||||
"net10.0": {
|
||||
"targetAlias": "net10.0",
|
||||
"projectReferences": {
|
||||
"e:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj": {
|
||||
"projectPath": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj"
|
||||
"E:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj": {
|
||||
"projectPath": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "TeczXo+31S0=",
|
||||
"dgSpecHash": "rWL1CDgJNu8=",
|
||||
"success": true,
|
||||
"projectFilePath": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Modules\\Chats\\Vortex.Modules.Chats.csproj",
|
||||
"projectFilePath": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Modules\\Chats\\Vortex.Modules.Chats.csproj",
|
||||
"expectedPackageFiles": [
|
||||
"C:\\Users\\HomePC\\.nuget\\packages\\mediatr\\12.0.1\\mediatr.12.0.1.nupkg.sha512",
|
||||
"C:\\Users\\HomePC\\.nuget\\packages\\mediatr.contracts\\2.0.1\\mediatr.contracts.2.0.1.nupkg.sha512",
|
||||
|
||||
@@ -12,6 +12,9 @@ public sealed class Story : Entity<Guid>
|
||||
public DateTime CreatedAt { get; private set; }
|
||||
public DateTime ExpiresAt { get; private set; }
|
||||
|
||||
private readonly List<StoryViewer> _viewers = new();
|
||||
public IReadOnlyCollection<StoryViewer> Viewers => _viewers.AsReadOnly();
|
||||
|
||||
private Story(Guid id, Guid userId, string type, string? mediaUrl, string? content, string? bgColor) : base(id)
|
||||
{
|
||||
UserId = userId;
|
||||
@@ -27,4 +30,10 @@ public sealed class Story : Entity<Guid>
|
||||
{
|
||||
return new Story(Guid.NewGuid(), userId, type, mediaUrl, content, bgColor);
|
||||
}
|
||||
|
||||
public void AddViewer(Guid userId)
|
||||
{
|
||||
if (_viewers.Any(v => v.UserId == userId)) return;
|
||||
_viewers.Add(new StoryViewer(Id, userId));
|
||||
}
|
||||
}
|
||||
|
||||
17
apps/server-net/src/Modules/Identity/Domain/StoryViewer.cs
Normal file
17
apps/server-net/src/Modules/Identity/Domain/StoryViewer.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Vortex.Shared.Kernel;
|
||||
|
||||
namespace Vortex.Modules.Identity.Domain;
|
||||
|
||||
public sealed class StoryViewer : Entity<Guid>
|
||||
{
|
||||
public Guid StoryId { get; private set; }
|
||||
public Guid UserId { get; private set; }
|
||||
public DateTime ViewedAt { get; private set; }
|
||||
|
||||
internal StoryViewer(Guid storyId, Guid userId) : base(Guid.NewGuid())
|
||||
{
|
||||
StoryId = storyId;
|
||||
UserId = userId;
|
||||
ViewedAt = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||
using Vortex.Shared.Kernel;
|
||||
using Vortex.Modules.Identity.Domain;
|
||||
|
||||
@@ -22,7 +23,14 @@ public sealed class IdentityDbContext : DbContext, IIdentityUnitOfWork
|
||||
|
||||
public DbSet<User> Users => Set<User>();
|
||||
public DbSet<Story> Stories => Set<Story>();
|
||||
public DbSet<StoryViewer> StoryViewers => Set<StoryViewer>();
|
||||
public DbSet<Friendship> Friendships => Set<Friendship>();
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
base.OnConfiguring(optionsBuilder);
|
||||
optionsBuilder.ConfigureWarnings(w => w.Ignore(RelationalEventId.PendingModelChangesWarning));
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
@@ -40,6 +48,17 @@ public sealed class IdentityDbContext : DbContext, IIdentityUnitOfWork
|
||||
builder.ToTable("Stories");
|
||||
builder.HasKey(s => s.Id);
|
||||
builder.Property(s => s.Type).IsRequired();
|
||||
builder.HasMany(s => s.Viewers)
|
||||
.WithOne()
|
||||
.HasForeignKey(v => v.StoryId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<StoryViewer>(builder =>
|
||||
{
|
||||
builder.ToTable("StoryViewers");
|
||||
builder.HasKey(v => v.Id);
|
||||
builder.HasIndex(v => new { v.StoryId, v.UserId }).IsUnique();
|
||||
});
|
||||
|
||||
modelBuilder.Entity<User>(builder =>
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
// <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.Identity.Infrastructure.Persistence;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Vortex.Modules.Identity.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
[DbContext(typeof(IdentityDbContext))]
|
||||
[Migration("20260312174453_AddHideStoryViewsAndFixStoryViewer")]
|
||||
partial class AddHideStoryViewsAndFixStoryViewer
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("identity")
|
||||
.HasAnnotation("ProductVersion", "10.0.4")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Vortex.Modules.Identity.Domain.Friendship", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid>("FriendId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId", "FriendId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Friendships", "identity");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Vortex.Modules.Identity.Domain.Story", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("BgColor")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<DateTime>("ExpiresAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("MediaUrl")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Stories", "identity");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Vortex.Modules.Identity.Domain.StoryViewer", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("StoryId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTime>("ViewedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("StoryId", "UserId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("StoryViewers", "identity");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Vortex.Modules.Identity.Domain.User", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Avatar")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("character varying(500)");
|
||||
|
||||
b.Property<string>("Bio")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("character varying(500)");
|
||||
|
||||
b.Property<DateTime?>("Birthday")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<bool>("HideStoryViews")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("boolean")
|
||||
.HasDefaultValue(false);
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Username")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Users", "identity");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Vortex.Modules.Identity.Domain.StoryViewer", b =>
|
||||
{
|
||||
b.HasOne("Vortex.Modules.Identity.Domain.Story", null)
|
||||
.WithMany("Viewers")
|
||||
.HasForeignKey("StoryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Vortex.Modules.Identity.Domain.Story", b =>
|
||||
{
|
||||
b.Navigation("Viewers");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Vortex.Modules.Identity.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddHideStoryViewsAndFixStoryViewer : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "StoryViewers",
|
||||
schema: "identity",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
StoryId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
UserId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
ViewedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_StoryViewers", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_StoryViewers_Stories_StoryId",
|
||||
column: x => x.StoryId,
|
||||
principalSchema: "identity",
|
||||
principalTable: "Stories",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_StoryViewers_StoryId_UserId",
|
||||
schema: "identity",
|
||||
table: "StoryViewers",
|
||||
columns: new[] { "StoryId", "UserId" },
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "StoryViewers",
|
||||
schema: "identity");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,6 +82,29 @@ namespace Vortex.Modules.Identity.Infrastructure.Persistence.Migrations
|
||||
b.ToTable("Stories", "identity");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Vortex.Modules.Identity.Domain.StoryViewer", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("StoryId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTime>("ViewedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("StoryId", "UserId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("StoryViewers", "identity");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Vortex.Modules.Identity.Domain.User", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@@ -132,6 +155,20 @@ namespace Vortex.Modules.Identity.Infrastructure.Persistence.Migrations
|
||||
|
||||
b.ToTable("Users", "identity");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Vortex.Modules.Identity.Domain.StoryViewer", b =>
|
||||
{
|
||||
b.HasOne("Vortex.Modules.Identity.Domain.Story", null)
|
||||
.WithMany("Viewers")
|
||||
.HasForeignKey("StoryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Vortex.Modules.Identity.Domain.Story", b =>
|
||||
{
|
||||
b.Navigation("Viewers");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
|
||||
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+d025c98e61cc08a53e6ebc62544d35be73a039e7")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+1ec6b438d29413482e1393c8ee5717f1211bd2c1")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("Vortex.Modules.Identity")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("Vortex.Modules.Identity")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
@@ -1 +1 @@
|
||||
c3cadb6f052217ce8055d22cc45365823011381722fc02323060802003a463fa
|
||||
9d9ffd258fd1015aa25ef1b7c83a7e79e77e5f90a31d4742aa8a559cc7698555
|
||||
|
||||
@@ -10,7 +10,7 @@ build_property.PlatformNeutralAssembly =
|
||||
build_property.EnforceExtendedAnalyzerRules =
|
||||
build_property._SupportedPlatformList = Linux,macOS,Windows
|
||||
build_property.RootNamespace = Vortex.Modules.Identity
|
||||
build_property.ProjectDir = e:\GIT\forkmessager\apps\server-net\src\Modules\Identity\
|
||||
build_property.ProjectDir = E:\GIT\forkmessager\apps\server-net\src\Modules\Identity\
|
||||
build_property.EnableComHosting =
|
||||
build_property.EnableGeneratedComInterfaceComImportInterop =
|
||||
build_property.EffectiveAnalysisLevelStyle = 10.0
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
||||
9c4a98722858a19d09e1cea0910af6e8695a81fc69a145e71388b513aa5d75c8
|
||||
f5710cf9cae3bcef5bc636925e0d10679b38fdce0a7e8d6824b7b47ab526589d
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,17 +1,17 @@
|
||||
{
|
||||
"format": 1,
|
||||
"restore": {
|
||||
"e:\\GIT\\forkmessager\\apps\\server-net\\src\\Modules\\Identity\\Vortex.Modules.Identity.csproj": {}
|
||||
"E:\\GIT\\forkmessager\\apps\\server-net\\src\\Modules\\Identity\\Vortex.Modules.Identity.csproj": {}
|
||||
},
|
||||
"projects": {
|
||||
"e:\\GIT\\forkmessager\\apps\\server-net\\src\\Modules\\Identity\\Vortex.Modules.Identity.csproj": {
|
||||
"E:\\GIT\\forkmessager\\apps\\server-net\\src\\Modules\\Identity\\Vortex.Modules.Identity.csproj": {
|
||||
"version": "1.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Modules\\Identity\\Vortex.Modules.Identity.csproj",
|
||||
"projectUniqueName": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Modules\\Identity\\Vortex.Modules.Identity.csproj",
|
||||
"projectName": "Vortex.Modules.Identity",
|
||||
"projectPath": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Modules\\Identity\\Vortex.Modules.Identity.csproj",
|
||||
"projectPath": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Modules\\Identity\\Vortex.Modules.Identity.csproj",
|
||||
"packagesPath": "C:\\Users\\HomePC\\.nuget\\packages\\",
|
||||
"outputPath": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Modules\\Identity\\obj\\",
|
||||
"outputPath": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Modules\\Identity\\obj\\",
|
||||
"projectStyle": "PackageReference",
|
||||
"fallbackFolders": [
|
||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
||||
@@ -33,8 +33,8 @@
|
||||
"net10.0": {
|
||||
"targetAlias": "net10.0",
|
||||
"projectReferences": {
|
||||
"e:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj": {
|
||||
"projectPath": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj"
|
||||
"E:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj": {
|
||||
"projectPath": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -534,14 +534,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"e:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj": {
|
||||
"E:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj": {
|
||||
"version": "1.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj",
|
||||
"projectUniqueName": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj",
|
||||
"projectName": "Vortex.Shared.Kernel",
|
||||
"projectPath": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj",
|
||||
"projectPath": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj",
|
||||
"packagesPath": "C:\\Users\\HomePC\\.nuget\\packages\\",
|
||||
"outputPath": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\obj\\",
|
||||
"outputPath": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\obj\\",
|
||||
"projectStyle": "PackageReference",
|
||||
"fallbackFolders": [
|
||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
||||
|
||||
@@ -1201,11 +1201,11 @@
|
||||
"project": {
|
||||
"version": "1.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Modules\\Identity\\Vortex.Modules.Identity.csproj",
|
||||
"projectUniqueName": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Modules\\Identity\\Vortex.Modules.Identity.csproj",
|
||||
"projectName": "Vortex.Modules.Identity",
|
||||
"projectPath": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Modules\\Identity\\Vortex.Modules.Identity.csproj",
|
||||
"projectPath": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Modules\\Identity\\Vortex.Modules.Identity.csproj",
|
||||
"packagesPath": "C:\\Users\\HomePC\\.nuget\\packages\\",
|
||||
"outputPath": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Modules\\Identity\\obj\\",
|
||||
"outputPath": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Modules\\Identity\\obj\\",
|
||||
"projectStyle": "PackageReference",
|
||||
"fallbackFolders": [
|
||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
||||
@@ -1227,8 +1227,8 @@
|
||||
"net10.0": {
|
||||
"targetAlias": "net10.0",
|
||||
"projectReferences": {
|
||||
"e:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj": {
|
||||
"projectPath": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj"
|
||||
"E:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj": {
|
||||
"projectPath": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "LFvHqUy/0DQ=",
|
||||
"dgSpecHash": "zJhKnu5UrIw=",
|
||||
"success": true,
|
||||
"projectFilePath": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Modules\\Identity\\Vortex.Modules.Identity.csproj",
|
||||
"projectFilePath": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Modules\\Identity\\Vortex.Modules.Identity.csproj",
|
||||
"expectedPackageFiles": [
|
||||
"C:\\Users\\HomePC\\.nuget\\packages\\bcrypt.net-next\\4.1.0\\bcrypt.net-next.4.1.0.nupkg.sha512",
|
||||
"C:\\Users\\HomePC\\.nuget\\packages\\mediatr\\12.0.1\\mediatr.12.0.1.nupkg.sha512",
|
||||
|
||||
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+d025c98e61cc08a53e6ebc62544d35be73a039e7")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+1ec6b438d29413482e1393c8ee5717f1211bd2c1")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("Vortex.Shared.Infrastructure")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("Vortex.Shared.Infrastructure")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
@@ -1 +1 @@
|
||||
adb7ba56083ad67b90d8f8374db47603dfc37300aa26f470695f98b925535a7f
|
||||
e0bbb37b072e496f3944a8671d38c2aa23b6d62d5e51acb6547971f52a1ef575
|
||||
|
||||
@@ -10,7 +10,7 @@ build_property.PlatformNeutralAssembly =
|
||||
build_property.EnforceExtendedAnalyzerRules =
|
||||
build_property._SupportedPlatformList = Linux,macOS,Windows
|
||||
build_property.RootNamespace = Vortex.Shared.Infrastructure
|
||||
build_property.ProjectDir = e:\GIT\forkmessager\apps\server-net\src\Shared\Vortex.Shared.Infrastructure\
|
||||
build_property.ProjectDir = E:\GIT\forkmessager\apps\server-net\src\Shared\Vortex.Shared.Infrastructure\
|
||||
build_property.EnableComHosting =
|
||||
build_property.EnableGeneratedComInterfaceComImportInterop =
|
||||
build_property.EffectiveAnalysisLevelStyle = 10.0
|
||||
|
||||
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,17 +1,17 @@
|
||||
{
|
||||
"format": 1,
|
||||
"restore": {
|
||||
"e:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Infrastructure\\Vortex.Shared.Infrastructure.csproj": {}
|
||||
"E:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Infrastructure\\Vortex.Shared.Infrastructure.csproj": {}
|
||||
},
|
||||
"projects": {
|
||||
"e:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Infrastructure\\Vortex.Shared.Infrastructure.csproj": {
|
||||
"E:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Infrastructure\\Vortex.Shared.Infrastructure.csproj": {
|
||||
"version": "1.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Infrastructure\\Vortex.Shared.Infrastructure.csproj",
|
||||
"projectUniqueName": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Infrastructure\\Vortex.Shared.Infrastructure.csproj",
|
||||
"projectName": "Vortex.Shared.Infrastructure",
|
||||
"projectPath": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Infrastructure\\Vortex.Shared.Infrastructure.csproj",
|
||||
"projectPath": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Infrastructure\\Vortex.Shared.Infrastructure.csproj",
|
||||
"packagesPath": "C:\\Users\\HomePC\\.nuget\\packages\\",
|
||||
"outputPath": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Infrastructure\\obj\\",
|
||||
"outputPath": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Infrastructure\\obj\\",
|
||||
"projectStyle": "PackageReference",
|
||||
"fallbackFolders": [
|
||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
||||
@@ -33,8 +33,8 @@
|
||||
"net10.0": {
|
||||
"targetAlias": "net10.0",
|
||||
"projectReferences": {
|
||||
"e:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj": {
|
||||
"projectPath": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj"
|
||||
"E:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj": {
|
||||
"projectPath": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -510,14 +510,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"e:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj": {
|
||||
"E:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj": {
|
||||
"version": "1.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj",
|
||||
"projectUniqueName": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj",
|
||||
"projectName": "Vortex.Shared.Kernel",
|
||||
"projectPath": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj",
|
||||
"projectPath": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj",
|
||||
"packagesPath": "C:\\Users\\HomePC\\.nuget\\packages\\",
|
||||
"outputPath": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\obj\\",
|
||||
"outputPath": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\obj\\",
|
||||
"projectStyle": "PackageReference",
|
||||
"fallbackFolders": [
|
||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
||||
|
||||
@@ -992,11 +992,11 @@
|
||||
"project": {
|
||||
"version": "1.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Infrastructure\\Vortex.Shared.Infrastructure.csproj",
|
||||
"projectUniqueName": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Infrastructure\\Vortex.Shared.Infrastructure.csproj",
|
||||
"projectName": "Vortex.Shared.Infrastructure",
|
||||
"projectPath": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Infrastructure\\Vortex.Shared.Infrastructure.csproj",
|
||||
"projectPath": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Infrastructure\\Vortex.Shared.Infrastructure.csproj",
|
||||
"packagesPath": "C:\\Users\\HomePC\\.nuget\\packages\\",
|
||||
"outputPath": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Infrastructure\\obj\\",
|
||||
"outputPath": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Infrastructure\\obj\\",
|
||||
"projectStyle": "PackageReference",
|
||||
"fallbackFolders": [
|
||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
||||
@@ -1018,8 +1018,8 @@
|
||||
"net10.0": {
|
||||
"targetAlias": "net10.0",
|
||||
"projectReferences": {
|
||||
"e:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj": {
|
||||
"projectPath": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj"
|
||||
"E:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj": {
|
||||
"projectPath": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "mujERCxuDP8=",
|
||||
"dgSpecHash": "+h84t9fYbLg=",
|
||||
"success": true,
|
||||
"projectFilePath": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Infrastructure\\Vortex.Shared.Infrastructure.csproj",
|
||||
"projectFilePath": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Infrastructure\\Vortex.Shared.Infrastructure.csproj",
|
||||
"expectedPackageFiles": [
|
||||
"C:\\Users\\HomePC\\.nuget\\packages\\mediatr\\12.0.1\\mediatr.12.0.1.nupkg.sha512",
|
||||
"C:\\Users\\HomePC\\.nuget\\packages\\mediatr.contracts\\2.0.1\\mediatr.contracts.2.0.1.nupkg.sha512",
|
||||
|
||||
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+d025c98e61cc08a53e6ebc62544d35be73a039e7")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+1ec6b438d29413482e1393c8ee5717f1211bd2c1")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("Vortex.Shared.Kernel")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("Vortex.Shared.Kernel")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
@@ -1 +1 @@
|
||||
2ab218fedb325bf279a8607f3ddd12468bb20cfa3895570bcfa7cdb1e276425f
|
||||
b12266e8303643e0701f21b3366bc23363791cc5e63972b801eea2f6405398dc
|
||||
|
||||
@@ -10,7 +10,7 @@ build_property.PlatformNeutralAssembly =
|
||||
build_property.EnforceExtendedAnalyzerRules =
|
||||
build_property._SupportedPlatformList = Linux,macOS,Windows
|
||||
build_property.RootNamespace = Vortex.Shared.Kernel
|
||||
build_property.ProjectDir = e:\GIT\forkmessager\apps\server-net\src\Shared\Vortex.Shared.Kernel\
|
||||
build_property.ProjectDir = E:\GIT\forkmessager\apps\server-net\src\Shared\Vortex.Shared.Kernel\
|
||||
build_property.EnableComHosting =
|
||||
build_property.EnableGeneratedComInterfaceComImportInterop =
|
||||
build_property.EffectiveAnalysisLevelStyle = 10.0
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,17 +1,17 @@
|
||||
{
|
||||
"format": 1,
|
||||
"restore": {
|
||||
"e:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj": {}
|
||||
"E:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj": {}
|
||||
},
|
||||
"projects": {
|
||||
"e:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj": {
|
||||
"E:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj": {
|
||||
"version": "1.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj",
|
||||
"projectUniqueName": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj",
|
||||
"projectName": "Vortex.Shared.Kernel",
|
||||
"projectPath": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj",
|
||||
"projectPath": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj",
|
||||
"packagesPath": "C:\\Users\\HomePC\\.nuget\\packages\\",
|
||||
"outputPath": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\obj\\",
|
||||
"outputPath": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\obj\\",
|
||||
"projectStyle": "PackageReference",
|
||||
"fallbackFolders": [
|
||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "SdDYXwY4FZU=",
|
||||
"dgSpecHash": "qfmzINr+8lA=",
|
||||
"success": true,
|
||||
"projectFilePath": "e:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj",
|
||||
"projectFilePath": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Shared\\Vortex.Shared.Kernel\\Vortex.Shared.Kernel.csproj",
|
||||
"expectedPackageFiles": [
|
||||
"C:\\Users\\HomePC\\.nuget\\packages\\mediatr\\12.0.1\\mediatr.12.0.1.nupkg.sha512",
|
||||
"C:\\Users\\HomePC\\.nuget\\packages\\mediatr.contracts\\2.0.1\\mediatr.contracts.2.0.1.nupkg.sha512",
|
||||
|
||||
@@ -74,6 +74,22 @@ public sealed class ChatsController : ControllerBase
|
||||
{
|
||||
var m = chatMessages.First();
|
||||
var senderObj = await _userRepository.GetByIdAsync(m.SenderId, ct);
|
||||
|
||||
var reactionsWithUser = new List<object>();
|
||||
foreach (var r in m.Reactions)
|
||||
{
|
||||
var rUser = await _userRepository.GetByIdAsync(r.UserId, ct);
|
||||
reactionsWithUser.Add(new
|
||||
{
|
||||
id = r.Id,
|
||||
emoji = r.Emoji,
|
||||
userId = r.UserId,
|
||||
user = rUser != null
|
||||
? new { id = rUser.Id, username = rUser.Username, displayName = rUser.DisplayName }
|
||||
: new { id = r.UserId, username = "unknown", displayName = "Unknown" }
|
||||
});
|
||||
}
|
||||
|
||||
messagesList.Add(new
|
||||
{
|
||||
m.Id,
|
||||
@@ -93,7 +109,7 @@ public sealed class ChatsController : ControllerBase
|
||||
displayName = senderObj.DisplayName,
|
||||
avatar = senderObj.Avatar
|
||||
} : new { id = m.SenderId, username = "unknown", displayName = "Unknown", avatar = (string?)null },
|
||||
Reactions = new List<object>(),
|
||||
reactions = reactionsWithUser,
|
||||
ReadBy = m.ReadBy.Select(r => new { userId = r.UserId }).ToList()
|
||||
});
|
||||
}
|
||||
@@ -131,7 +147,7 @@ public sealed class ChatsController : ControllerBase
|
||||
[HttpPost("personal")]
|
||||
public async Task<IActionResult> CreatePersonal([FromBody] CreatePersonalChatRequest request, CancellationToken ct)
|
||||
{
|
||||
var command = new CreateChatCommand((string?)null, ChatType.Personal, new List<Guid> { _userContext.UserId, request.UserId });
|
||||
var command = new CreateChatCommand(string.Empty, ChatType.Personal, new List<Guid> { _userContext.UserId, request.UserId });
|
||||
Result<Guid> result = await _sender.Send(command, ct);
|
||||
|
||||
if (result.IsFailure) return BadRequest(result.Error);
|
||||
@@ -142,7 +158,13 @@ public sealed class ChatsController : ControllerBase
|
||||
[HttpPost("group")]
|
||||
public async Task<IActionResult> CreateGroup([FromBody] CreateGroupChatRequest request, CancellationToken ct)
|
||||
{
|
||||
var command = new CreateChatCommand(request.Name, ChatType.Group, request.MemberIds);
|
||||
var memberIds = request.MemberIds.ToList();
|
||||
if (!memberIds.Contains(_userContext.UserId))
|
||||
{
|
||||
memberIds.Add(_userContext.UserId);
|
||||
}
|
||||
|
||||
var command = new CreateChatCommand(request.Name, ChatType.Group, memberIds);
|
||||
Result<Guid> result = await _sender.Send(command, ct);
|
||||
|
||||
if (result.IsFailure) return BadRequest(result.Error);
|
||||
|
||||
@@ -30,6 +30,9 @@ public sealed class MessagesController : ControllerBase
|
||||
{
|
||||
var messages = await messageRepository.GetChatMessagesAsync(chatId, 50, 0, ct);
|
||||
|
||||
// Filter out messages deleted for this user
|
||||
messages = messages.Where(m => !m.DeletedByUsers.Contains(_userContext.UserId)).ToList();
|
||||
|
||||
// Collect all user IDs needed (senders, forwarded from)
|
||||
var userIds = messages.Select(m => m.SenderId).ToList();
|
||||
userIds.AddRange(messages.Where(m => m.ForwardedFromId.HasValue).Select(m => m.ForwardedFromId!.Value));
|
||||
@@ -61,6 +64,21 @@ public sealed class MessagesController : ControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
var reactionsWithUser = new List<object>();
|
||||
foreach (var r in m.Reactions)
|
||||
{
|
||||
var rUser = await _userRepository.GetByIdAsync(r.UserId, ct);
|
||||
reactionsWithUser.Add(new
|
||||
{
|
||||
id = r.Id,
|
||||
emoji = r.Emoji,
|
||||
userId = r.UserId,
|
||||
user = rUser != null
|
||||
? new { id = rUser.Id, username = rUser.Username, displayName = rUser.DisplayName }
|
||||
: new { id = r.UserId, username = "unknown", displayName = "Unknown" }
|
||||
});
|
||||
}
|
||||
|
||||
result.Add(new
|
||||
{
|
||||
m.Id,
|
||||
@@ -94,7 +112,7 @@ public sealed class MessagesController : ControllerBase
|
||||
displayName = s.DisplayName,
|
||||
avatar = s.Avatar
|
||||
} : new { id = m.SenderId, username = "unknown", displayName = "Unknown", avatar = (string?)null },
|
||||
reactions = m.Reactions.Select(r => new { r.UserId, r.Emoji }).ToList(),
|
||||
reactions = reactionsWithUser,
|
||||
readBy = m.ReadBy.Select(r => new { userId = r.UserId }).ToList()
|
||||
});
|
||||
}
|
||||
@@ -107,6 +125,9 @@ public sealed class MessagesController : ControllerBase
|
||||
{
|
||||
var messages = await messageRepository.SearchMessagesAsync(q, chatId, ct);
|
||||
|
||||
// Filter out messages deleted for this user
|
||||
messages = messages.Where(m => !m.DeletedByUsers.Contains(_userContext.UserId)).ToList();
|
||||
|
||||
var userIds = messages.Select(m => m.SenderId).ToList();
|
||||
userIds.AddRange(messages.Where(m => m.ForwardedFromId.HasValue).Select(m => m.ForwardedFromId!.Value));
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Vortex.Modules.Identity.Domain;
|
||||
using Vortex.Modules.Identity.Infrastructure.Persistence;
|
||||
@@ -16,12 +17,18 @@ public sealed class StoriesController : ControllerBase
|
||||
private readonly IdentityDbContext _context;
|
||||
private readonly IUserContext _userContext;
|
||||
private readonly IUserRepository _userRepository;
|
||||
private readonly Microsoft.AspNetCore.SignalR.IHubContext<Vortex.Modules.Chats.Infrastructure.SignalR.ChatHub> _hubContext;
|
||||
|
||||
public StoriesController(IdentityDbContext context, IUserContext userContext, IUserRepository userRepository)
|
||||
public StoriesController(
|
||||
IdentityDbContext context,
|
||||
IUserContext userContext,
|
||||
IUserRepository userRepository,
|
||||
Microsoft.AspNetCore.SignalR.IHubContext<Vortex.Modules.Chats.Infrastructure.SignalR.ChatHub> hubContext)
|
||||
{
|
||||
_context = context;
|
||||
_userContext = userContext;
|
||||
_userRepository = userRepository;
|
||||
_hubContext = hubContext;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
@@ -37,17 +44,25 @@ public sealed class StoriesController : ControllerBase
|
||||
friendIds.Add(currentUserId);
|
||||
|
||||
var stories = await _context.Stories
|
||||
.Include(s => s.Viewers)
|
||||
.Where(s => s.ExpiresAt > DateTime.UtcNow && friendIds.Contains(s.UserId))
|
||||
.OrderByDescending(s => s.CreatedAt)
|
||||
.ToListAsync(ct);
|
||||
|
||||
var groups = stories.GroupBy(s => s.UserId);
|
||||
var groups = stories.GroupBy(s => s.UserId).ToList();
|
||||
var result = new List<object>();
|
||||
|
||||
var userIds = groups.Select(g => g.Key).ToList();
|
||||
var userMap = new Dictionary<Guid, dynamic>();
|
||||
foreach (var uid in userIds)
|
||||
{
|
||||
var u = await _userRepository.GetByIdAsync(uid, ct);
|
||||
if (u != null) userMap[uid] = u;
|
||||
}
|
||||
|
||||
foreach (var group in groups)
|
||||
{
|
||||
var user = await _userRepository.GetByIdAsync(group.Key, ct);
|
||||
if (user == null) continue;
|
||||
if (!userMap.TryGetValue(group.Key, out var user)) continue;
|
||||
|
||||
result.Add(new
|
||||
{
|
||||
@@ -67,10 +82,10 @@ public sealed class StoriesController : ControllerBase
|
||||
bgColor = s.BgColor,
|
||||
createdAt = s.CreatedAt,
|
||||
expiresAt = s.ExpiresAt,
|
||||
viewCount = 0,
|
||||
viewed = false
|
||||
}).ToList(),
|
||||
hasUnviewed = true
|
||||
viewCount = s.Viewers.Count,
|
||||
viewed = s.Viewers.Any(v => v.UserId == currentUserId)
|
||||
}).OrderBy(s => s.createdAt).ToList(),
|
||||
hasUnviewed = group.Any(s => !s.Viewers.Any(v => v.UserId == currentUserId))
|
||||
});
|
||||
}
|
||||
|
||||
@@ -99,6 +114,84 @@ public sealed class StoriesController : ControllerBase
|
||||
|
||||
return Ok(new { id = story.Id });
|
||||
}
|
||||
|
||||
[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))
|
||||
{
|
||||
story.AddViewer(_userContext.UserId);
|
||||
await _context.SaveChangesAsync(ct);
|
||||
|
||||
// Notify owner via SignalR
|
||||
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
|
||||
}, ct);
|
||||
}
|
||||
|
||||
return Ok(new { message = "Story viewed" });
|
||||
}
|
||||
|
||||
[HttpGet("{id}/viewers")]
|
||||
public async Task<IActionResult> GetStoryViewers(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 Forbid();
|
||||
|
||||
var viewerIds = story.Viewers.Select(v => v.UserId).ToList();
|
||||
var viewers = new List<object>();
|
||||
|
||||
foreach (var viewerId in viewerIds)
|
||||
{
|
||||
var user = await _userRepository.GetByIdAsync(viewerId, ct);
|
||||
if (user == null) continue;
|
||||
|
||||
var viewerRecord = story.Viewers.First(v => v.UserId == viewerId);
|
||||
|
||||
viewers.Add(new
|
||||
{
|
||||
userId = user.Id,
|
||||
username = user.Username,
|
||||
displayName = user.DisplayName,
|
||||
avatar = user.Avatar,
|
||||
viewedAt = viewerRecord.ViewedAt
|
||||
});
|
||||
}
|
||||
|
||||
return Ok(viewers);
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> DeleteStory(Guid id, CancellationToken ct)
|
||||
{
|
||||
var story = await _context.Stories.FindAsync(new object[] { id }, ct);
|
||||
if (story == null) return NotFound();
|
||||
if (story.UserId != _userContext.UserId) return Forbid();
|
||||
|
||||
_context.Stories.Remove(story);
|
||||
await _context.SaveChangesAsync(ct);
|
||||
|
||||
return Ok(new { message = "Story deleted" });
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record CreateStoryRequest(string Type, string? MediaUrl, string? Content, string? BgColor);
|
||||
|
||||
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+d025c98e61cc08a53e6ebc62544d35be73a039e7")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+1ec6b438d29413482e1393c8ee5717f1211bd2c1")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("Vortex.Host")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("Vortex.Host")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
@@ -1 +1 @@
|
||||
7eb1f40e06ff22545aecfb70096f70b5f91da82514bc2bf01247c568eaabc058
|
||||
c8308646f5647671185661b28c5c3eabe4dd11328d5c7402bbee505991c08d6d
|
||||
|
||||
Binary file not shown.
@@ -1 +1 @@
|
||||
11d26d1b87f3679d8be8eeb05168fa71c45a3d83b12aa28eb232183b34639103
|
||||
a0a268746b849e72488e8608dc7529251d735d933091a22e5f93d38662ef7534
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user