Доработки профиля
@@ -28,9 +28,11 @@ public sealed class CreateChatCommandHandler : ICommandHandler<CreateChatCommand
|
||||
{
|
||||
var chat = Chat.Create(request.Name, request.Type);
|
||||
|
||||
foreach (var userId in request.MemberIds)
|
||||
for (int i = 0; i < request.MemberIds.Count; i++)
|
||||
{
|
||||
chat.AddMember(userId, ChatRole.Member);
|
||||
var userId = request.MemberIds[i];
|
||||
var role = (i == 0 && request.Type == ChatType.Group) ? ChatRole.Admin : ChatRole.Member;
|
||||
chat.AddMember(userId, role);
|
||||
}
|
||||
|
||||
_chatRepository.Add(chat);
|
||||
|
||||
@@ -32,17 +32,19 @@ public sealed class Chat : AggregateRoot<Guid>
|
||||
{
|
||||
public ChatType Type { get; private set; }
|
||||
public string? Name { get; private set; }
|
||||
public string? Description { get; private set; }
|
||||
public string? Avatar { get; private set; }
|
||||
public DateTime CreatedAt { get; private set; }
|
||||
|
||||
private readonly List<ChatMember> _members = new();
|
||||
public IReadOnlyCollection<ChatMember> Members => _members.AsReadOnly();
|
||||
|
||||
private Chat(Guid id, ChatType type, string? name, string? avatar) : base(id)
|
||||
private Chat(Guid id, ChatType type, string? name, string? avatar, string? description = null) : base(id)
|
||||
{
|
||||
Type = type;
|
||||
Name = name;
|
||||
Avatar = avatar;
|
||||
Description = description;
|
||||
CreatedAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
@@ -69,9 +71,9 @@ public sealed class Chat : AggregateRoot<Guid>
|
||||
/// <summary>
|
||||
/// Фабричный метод для создания чата.
|
||||
/// </summary>
|
||||
public static Chat Create(string? name, ChatType type, string? avatar = null)
|
||||
public static Chat Create(string? name, ChatType type, string? avatar = null, string? description = null)
|
||||
{
|
||||
var chat = new Chat(Guid.NewGuid(), type, name, avatar);
|
||||
var chat = new Chat(Guid.NewGuid(), type, name, avatar, description);
|
||||
chat.RaiseDomainEvent(new ChatCreatedDomainEvent(chat));
|
||||
return chat;
|
||||
}
|
||||
@@ -91,6 +93,8 @@ public sealed class Chat : AggregateRoot<Guid>
|
||||
|
||||
public void UpdateName(string name) => Name = name;
|
||||
|
||||
public void UpdateDescription(string? description) => Description = description;
|
||||
|
||||
public void UpdateAvatar(string? avatarUrl) => Avatar = avatarUrl;
|
||||
}
|
||||
|
||||
|
||||
@@ -90,8 +90,10 @@ public sealed class MessageSentDomainEventHandler : INotificationHandler<Message
|
||||
Size = m.Size
|
||||
}).ToList(),
|
||||
Sender = senderObj,
|
||||
Reactions = new List<object>(),
|
||||
ReadBy = new List<object>()
|
||||
ReadBy = new List<object>(),
|
||||
message.StoryId,
|
||||
message.StoryMediaUrl,
|
||||
message.StoryMediaType
|
||||
}, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
// <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("20260313204219_AddChatDescription")]
|
||||
partial class AddChatDescription
|
||||
{
|
||||
/// <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>("Description")
|
||||
.HasColumnType("text");
|
||||
|
||||
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<Guid?>("StoryId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("StoryMediaType")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("StoryMediaUrl")
|
||||
.HasColumnType("text");
|
||||
|
||||
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,30 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Vortex.Modules.Chats.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddChatDescription : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Description",
|
||||
schema: "chats",
|
||||
table: "Chats",
|
||||
type: "text",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Description",
|
||||
schema: "chats",
|
||||
table: "Chats");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,9 @@ namespace Vortex.Modules.Chats.Infrastructure.Persistence.Migrations
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("text");
|
||||
|
||||
|
||||
@@ -383,8 +383,9 @@ public sealed class ChatHub : Hub
|
||||
}
|
||||
|
||||
[HubMethodName("group_call_leave")]
|
||||
public async Task GroupCallLeave(string chatId)
|
||||
public async Task GroupCallLeave(GroupLeaveRequest request)
|
||||
{
|
||||
var chatId = request.ChatId;
|
||||
var userId = _userContext.UserId.ToString();
|
||||
if (_groupCallParticipants.TryGetValue(chatId, out var participants))
|
||||
{
|
||||
@@ -457,6 +458,84 @@ public sealed class ChatHub : Hub
|
||||
});
|
||||
}
|
||||
|
||||
[HubMethodName("group_call_status")]
|
||||
public async Task GroupCallStatus(GroupCallStatusRequest request)
|
||||
{
|
||||
var chatId = request.ChatId;
|
||||
var userId = _userContext.UserId.ToString();
|
||||
|
||||
if (_groupCallParticipants.TryGetValue(chatId, out var participants))
|
||||
{
|
||||
if (participants.TryGetValue(userId, out var info))
|
||||
{
|
||||
// Update local state if needed (e.g. muted status)
|
||||
participants[userId] = info with
|
||||
{
|
||||
IsMuted = request.IsMuted,
|
||||
IsVideoOff = request.IsVideoOff
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
await Clients.Group(request.ChatId).SendAsync("group_call_status_updated", new {
|
||||
chatId = request.ChatId,
|
||||
userId = Context.UserIdentifier,
|
||||
isMuted = request.IsMuted,
|
||||
isVideoOff = request.IsVideoOff
|
||||
});
|
||||
}
|
||||
|
||||
[HubMethodName("group_call_status_params")]
|
||||
public async Task GroupCallStatusParams(string chatId, bool isMuted, bool isVideoOff)
|
||||
{
|
||||
var userId = _userContext.UserId.ToString();
|
||||
|
||||
if (_groupCallParticipants.TryGetValue(chatId, out var participants))
|
||||
{
|
||||
if (participants.TryGetValue(userId, out var info))
|
||||
{
|
||||
// Update local state if needed (e.g. muted status)
|
||||
participants[userId] = info with
|
||||
{
|
||||
IsMuted = isMuted,
|
||||
IsVideoOff = isVideoOff
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
await Clients.Group(chatId).SendAsync("group_call_status_updated", new {
|
||||
chatId = chatId,
|
||||
userId = Context.UserIdentifier,
|
||||
isMuted = isMuted,
|
||||
isVideoOff = isVideoOff
|
||||
});
|
||||
}
|
||||
|
||||
[HubMethodName("get_group_call_status")]
|
||||
public async Task GetGroupCallStatus(GetGroupCallStatusRequest request)
|
||||
{
|
||||
var chatId = request.ChatId;
|
||||
if (_groupCallParticipants.TryGetValue(chatId, out var participants))
|
||||
{
|
||||
var others = participants.Values.ToList();
|
||||
_logger.LogInformation("Found {Count} participants for chat {ChatId}", others.Count, chatId);
|
||||
await Clients.Caller.SendAsync("group_call_active", new
|
||||
{
|
||||
chatId = chatId,
|
||||
participants = others.Select(p => p.Id).ToList()
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("No active call for chat {ChatId}", chatId);
|
||||
await Clients.Caller.SendAsync("group_call_active", new
|
||||
{
|
||||
chatId = chatId,
|
||||
participants = new List<string>()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[HubMethodName("screen_share_started")]
|
||||
public async Task ScreenShareStarted(string chatId)
|
||||
{
|
||||
@@ -523,7 +602,10 @@ public sealed class ChatHub : Hub
|
||||
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 ParticipantInfo(string Id, string Username, string DisplayName, string? Avatar, bool IsSharingScreen = false, bool IsMuted = false, bool IsVideoOff = false);
|
||||
public record GroupLeaveRequest(string ChatId);
|
||||
public record GetGroupCallStatusRequest(string ChatId);
|
||||
public record GroupCallStatusRequest(string ChatId, bool IsMuted, bool IsVideoOff);
|
||||
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);
|
||||
|
||||
@@ -12,6 +12,8 @@ using Vortex.Shared.Kernel;
|
||||
using Vortex.Modules.Identity.Domain;
|
||||
using Vortex.Modules.Identity.Application.Abstractions;
|
||||
using Vortex.Modules.Chats.Infrastructure.Persistence;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
|
||||
namespace Vortex.Host.Controllers;
|
||||
|
||||
@@ -100,6 +102,9 @@ public sealed class ChatsController : ControllerBase
|
||||
m.Type,
|
||||
m.ReplyToId,
|
||||
m.Quote,
|
||||
m.StoryId,
|
||||
m.StoryMediaUrl,
|
||||
m.StoryMediaType,
|
||||
m.IsEdited,
|
||||
m.IsDeleted,
|
||||
m.CreatedAt,
|
||||
@@ -119,7 +124,8 @@ public sealed class ChatsController : ControllerBase
|
||||
{
|
||||
id = c.Id,
|
||||
type = c.Type.ToString().ToLowerInvariant(),
|
||||
name = c.Type == ChatType.Favorites ? "Избранное" : c.Name,
|
||||
name = c.Type == ChatType.Favorites ? "Избранное" : (c.Type == ChatType.Personal ? null : c.Name),
|
||||
description = c.Description,
|
||||
avatar = c.Avatar,
|
||||
createdAt = c.CreatedAt,
|
||||
members = members,
|
||||
@@ -160,10 +166,11 @@ public sealed class ChatsController : ControllerBase
|
||||
public async Task<IActionResult> CreateGroup([FromBody] CreateGroupChatRequest request, CancellationToken ct)
|
||||
{
|
||||
var memberIds = request.MemberIds.ToList();
|
||||
if (!memberIds.Contains(_userContext.UserId))
|
||||
if (memberIds.Contains(_userContext.UserId))
|
||||
{
|
||||
memberIds.Add(_userContext.UserId);
|
||||
memberIds.Remove(_userContext.UserId);
|
||||
}
|
||||
memberIds.Insert(0, _userContext.UserId);
|
||||
|
||||
var command = new CreateChatCommand(request.Name, ChatType.Group, memberIds);
|
||||
Result<Guid> result = await _sender.Send(command, ct);
|
||||
@@ -191,6 +198,7 @@ public sealed class ChatsController : ControllerBase
|
||||
if (chat == null || !chat.Members.Any(m => m.UserId == _userContext.UserId)) return NotFound();
|
||||
|
||||
if (request.Name != null) chat.UpdateName(request.Name);
|
||||
if (request.Description != null) chat.UpdateDescription(request.Description);
|
||||
chatRepository.Update(chat);
|
||||
await uow.SaveChangesAsync(ct);
|
||||
|
||||
@@ -294,6 +302,47 @@ public sealed class ChatsController : ControllerBase
|
||||
return Ok(await MapChatAsync(id, ct));
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/avatar/crop")]
|
||||
public async Task<IActionResult> CropGroupAvatar(Guid id, [FromForm] IFormFile avatar, [FromForm] int x, [FromForm] int y, [FromForm] int width, [FromForm] int height, [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();
|
||||
if (avatar == null || avatar.Length == 0) return BadRequest("No file");
|
||||
|
||||
var uploadsPath = Path.Combine(Directory.GetCurrentDirectory(), "uploads", "avatars");
|
||||
if (!Directory.Exists(uploadsPath)) Directory.CreateDirectory(uploadsPath);
|
||||
|
||||
var fileName = $"{Guid.NewGuid()}.jpg";
|
||||
var filePath = Path.Combine(uploadsPath, fileName);
|
||||
|
||||
try
|
||||
{
|
||||
using (var inputStream = avatar.OpenReadStream())
|
||||
using (var image = await SixLabors.ImageSharp.Image.LoadAsync(inputStream))
|
||||
{
|
||||
int startX = Math.Max(0, Math.Min(x, image.Width - 1));
|
||||
int startY = Math.Max(0, Math.Min(y, image.Height - 1));
|
||||
int rectWidth = Math.Max(1, Math.Min(width, image.Width - startX));
|
||||
int rectHeight = Math.Max(1, Math.Min(height, image.Height - startY));
|
||||
|
||||
image.Mutate(ctx => ctx.Crop(new SixLabors.ImageSharp.Rectangle(startX, startY, rectWidth, rectHeight)));
|
||||
image.Mutate(ctx => ctx.Resize(400, 400));
|
||||
await image.SaveAsJpegAsync(filePath, ct);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, "Error processing image: " + ex.Message);
|
||||
}
|
||||
|
||||
var url = $"/uploads/avatars/{fileName}";
|
||||
chat.UpdateAvatar(url);
|
||||
chatRepository.Update(chat);
|
||||
await uow.SaveChangesAsync(ct);
|
||||
|
||||
return Ok(await MapChatAsync(id, ct));
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}/avatar")]
|
||||
public async Task<IActionResult> RemoveGroupAvatar(Guid id, [FromServices] IChatRepository chatRepository, [FromServices] IChatsUnitOfWork uow, CancellationToken ct)
|
||||
{
|
||||
@@ -310,6 +359,7 @@ public sealed class ChatsController : ControllerBase
|
||||
private async Task<object> MapChatAsync(Guid chatId, CancellationToken ct)
|
||||
{
|
||||
var chatRepository = HttpContext.RequestServices.GetRequiredService<IChatRepository>();
|
||||
var messageRepository = HttpContext.RequestServices.GetRequiredService<IMessageRepository>();
|
||||
var chat = await chatRepository.GetByIdAsync(chatId, ct);
|
||||
if (chat == null) return new { };
|
||||
|
||||
@@ -334,15 +384,48 @@ public sealed class ChatsController : ControllerBase
|
||||
});
|
||||
}
|
||||
|
||||
var chatMessages = await messageRepository.GetChatMessagesAsync(chatId, 1, 0, ct);
|
||||
var messagesList = new List<object>();
|
||||
if (chatMessages.Any())
|
||||
{
|
||||
var m = chatMessages.First();
|
||||
var senderObj = await _userRepository.GetByIdAsync(m.SenderId, ct);
|
||||
messagesList.Add(new
|
||||
{
|
||||
m.Id,
|
||||
m.ChatId,
|
||||
m.SenderId,
|
||||
m.Content,
|
||||
m.Type,
|
||||
m.ReplyToId,
|
||||
m.Quote,
|
||||
m.StoryId,
|
||||
m.StoryMediaUrl,
|
||||
m.StoryMediaType,
|
||||
m.IsEdited,
|
||||
m.IsDeleted,
|
||||
m.CreatedAt,
|
||||
Media = m.Media.ToList(),
|
||||
Sender = senderObj != null ? new {
|
||||
id = senderObj.Id,
|
||||
username = senderObj.Username,
|
||||
displayName = senderObj.DisplayName,
|
||||
avatar = senderObj.Avatar
|
||||
} : new { id = m.SenderId, username = "unknown", displayName = "Unknown", avatar = (string?)null },
|
||||
ReadBy = m.ReadBy.Select(r => new { userId = r.UserId }).ToList()
|
||||
});
|
||||
}
|
||||
|
||||
return new
|
||||
{
|
||||
id = chat.Id,
|
||||
type = chat.Type.ToString().ToLowerInvariant(),
|
||||
name = chat.Type == ChatType.Favorites ? "Избранное" : chat.Name,
|
||||
name = chat.Type == ChatType.Favorites ? "Избранное" : (chat.Type == ChatType.Personal ? null : chat.Name),
|
||||
description = chat.Description,
|
||||
avatar = chat.Avatar,
|
||||
createdAt = chat.CreatedAt,
|
||||
members = members,
|
||||
messages = new List<object>(),
|
||||
messages = messagesList,
|
||||
unreadCount = 0
|
||||
};
|
||||
}
|
||||
@@ -351,6 +434,6 @@ public sealed class ChatsController : ControllerBase
|
||||
public sealed record CreateChatRequest(string Name, ChatType Type, List<Guid> MemberIds);
|
||||
public sealed record CreatePersonalChatRequest(Guid UserId);
|
||||
public sealed record CreateGroupChatRequest(string Name, List<Guid> MemberIds);
|
||||
public sealed record UpdateChatRequest(string? Name);
|
||||
public sealed record UpdateChatRequest(string? Name, string? Description);
|
||||
public sealed record AddMembersRequest(List<Guid> UserIds);
|
||||
|
||||
|
||||
@@ -103,6 +103,8 @@ public sealed class MessagesController : ControllerBase
|
||||
avatar = fwd.Avatar
|
||||
} : null,
|
||||
storyId = m.StoryId,
|
||||
storyMediaUrl = m.StoryMediaUrl,
|
||||
storyMediaType = m.StoryMediaType,
|
||||
media = m.Media.Select(media => new {
|
||||
media.Id,
|
||||
media.Type,
|
||||
@@ -161,6 +163,9 @@ public sealed class MessagesController : ControllerBase
|
||||
displayName = fwd.DisplayName,
|
||||
avatar = fwd.Avatar
|
||||
} : null,
|
||||
storyId = m.StoryId,
|
||||
storyMediaUrl = m.StoryMediaUrl,
|
||||
storyMediaType = m.StoryMediaType,
|
||||
media = m.Media.Select(media => new {
|
||||
media.Id,
|
||||
media.Type,
|
||||
@@ -217,7 +222,7 @@ public sealed class MessagesController : ControllerBase
|
||||
{
|
||||
var linkRegex = new Regex(@"https?://[^\s]+", RegexOptions.IgnoreCase);
|
||||
var contentLinks = !string.IsNullOrEmpty(m.Content) ? linkRegex.Matches(m.Content).Select(match => match.Value).ToList() : new List<string>();
|
||||
var mediaLinks = m.Media.Where(media => media.Type?.ToLower() == "link").Select(media => media.Url).ToList();
|
||||
var mediaLinks = (m.Media ?? Enumerable.Empty<Media>()).Where(media => media.Type?.ToLower() == "link").Select(media => media.Url).ToList();
|
||||
var allLinks = contentLinks.Concat(mediaLinks).Distinct().ToList();
|
||||
|
||||
if (allLinks.Any())
|
||||
@@ -249,9 +254,12 @@ public sealed class MessagesController : ControllerBase
|
||||
var sender = await _userRepository.GetByIdAsync(m.SenderId, ct);
|
||||
result.Add(new
|
||||
{
|
||||
m.Id,
|
||||
m.ChatId,
|
||||
m.SenderId,
|
||||
m.ReplyToId,
|
||||
m.Quote,
|
||||
m.StoryId,
|
||||
m.StoryMediaUrl,
|
||||
m.StoryMediaType,
|
||||
m.IsEdited,
|
||||
m.Content,
|
||||
m.Type,
|
||||
m.CreatedAt,
|
||||
|
||||
@@ -3,6 +3,8 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Vortex.Modules.Identity.Domain;
|
||||
using Vortex.Shared.Kernel;
|
||||
using Vortex.Modules.Identity.Application.Abstractions;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
|
||||
namespace Vortex.Host.Controllers;
|
||||
|
||||
@@ -121,6 +123,58 @@ public sealed class UsersController : ControllerBase
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost("avatar/crop")]
|
||||
public async Task<IActionResult> CropAvatar([FromForm] IFormFile avatar, [FromForm] int x, [FromForm] int y, [FromForm] int width, [FromForm] int height, CancellationToken ct)
|
||||
{
|
||||
var user = await _userRepository.GetByIdAsync(_userContext.UserId, ct);
|
||||
if (user == null) return NotFound();
|
||||
|
||||
if (avatar == null || avatar.Length == 0) return BadRequest("No file");
|
||||
|
||||
var uploadsPath = Path.Combine(Directory.GetCurrentDirectory(), "uploads", "avatars");
|
||||
if (!Directory.Exists(uploadsPath)) Directory.CreateDirectory(uploadsPath);
|
||||
|
||||
var ext = ".jpg";
|
||||
var fileName = $"{Guid.NewGuid()}{ext}";
|
||||
var filePath = Path.Combine(uploadsPath, fileName);
|
||||
|
||||
try
|
||||
{
|
||||
using (var inputStream = avatar.OpenReadStream())
|
||||
using (var image = await SixLabors.ImageSharp.Image.LoadAsync(inputStream))
|
||||
{
|
||||
// Clamp coordinates to image bounds
|
||||
int startX = Math.Max(0, Math.Min(x, image.Width - 1));
|
||||
int startY = Math.Max(0, Math.Min(y, image.Height - 1));
|
||||
int rectWidth = Math.Max(1, Math.Min(width, image.Width - startX));
|
||||
int rectHeight = Math.Max(1, Math.Min(height, image.Height - startY));
|
||||
|
||||
image.Mutate(ctx => ctx.Crop(new SixLabors.ImageSharp.Rectangle(startX, startY, rectWidth, rectHeight)));
|
||||
image.Mutate(ctx => ctx.Resize(400, 400));
|
||||
|
||||
await image.SaveAsJpegAsync(filePath, ct);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, "Error processing image: " + ex.Message);
|
||||
}
|
||||
|
||||
var avatarUrl = $"/uploads/avatars/{fileName}";
|
||||
user.UpdateAvatar(avatarUrl);
|
||||
await _unitOfWork.SaveChangesAsync(ct);
|
||||
|
||||
return Ok(new {
|
||||
id = user.Id,
|
||||
username = user.Username,
|
||||
displayName = user.DisplayName,
|
||||
avatar = user.Avatar,
|
||||
bio = user.Bio,
|
||||
birthday = user.Birthday,
|
||||
createdAt = user.CreatedAt
|
||||
});
|
||||
}
|
||||
|
||||
[HttpDelete("avatar")]
|
||||
public async Task<IActionResult> DeleteAvatar(CancellationToken ct)
|
||||
{
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.5" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.16.0" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.16.0" />
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"Microsoft.EntityFrameworkCore.Design": "10.0.4",
|
||||
"Microsoft.IdentityModel.Tokens": "8.16.0",
|
||||
"Npgsql.EntityFrameworkCore.PostgreSQL": "10.0.0",
|
||||
"SixLabors.ImageSharp": "3.1.12",
|
||||
"Swashbuckle.AspNetCore": "10.1.5",
|
||||
"System.IdentityModel.Tokens.Jwt": "8.16.0",
|
||||
"Vortex.Modules.Chats": "1.0.0",
|
||||
@@ -741,6 +742,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"SixLabors.ImageSharp/3.1.12": {
|
||||
"runtime": {
|
||||
"lib/net6.0/SixLabors.ImageSharp.dll": {
|
||||
"assemblyVersion": "3.0.0.0",
|
||||
"fileVersion": "3.1.12.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Swashbuckle.AspNetCore/10.1.5": {
|
||||
"dependencies": {
|
||||
"Swashbuckle.AspNetCore.Swagger": "10.1.5",
|
||||
@@ -1252,6 +1261,13 @@
|
||||
"path": "npgsql.entityframeworkcore.postgresql/10.0.0",
|
||||
"hashPath": "npgsql.entityframeworkcore.postgresql.10.0.0.nupkg.sha512"
|
||||
},
|
||||
"SixLabors.ImageSharp/3.1.12": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-iAg6zifihXEFS/t7fiHhZBGAdCp3FavsF4i2ZIDp0JfeYeDVzvmlbY1CNhhIKimaIzrzSi5M/NBFcWvZT2rB/A==",
|
||||
"path": "sixlabors.imagesharp/3.1.12",
|
||||
"hashPath": "sixlabors.imagesharp.3.1.12.nupkg.sha512"
|
||||
},
|
||||
"Swashbuckle.AspNetCore/10.1.5": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
|
||||
@@ -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+ca1d88191ca628dbe2371abab56fb3cdfe386d65")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+010b96d362a728b28bae56836e464103371899f4")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("Vortex.Host")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("Vortex.Host")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
@@ -1 +1 @@
|
||||
f3d52bfd2b858d5d383380a9e4100efe419d613a4ecf13cf06c4ad546122d06b
|
||||
42cfa915aa235da6705c7cf95cb1a14d6010145730d282552fab9d0c3500b1eb
|
||||
|
||||
@@ -1 +1 @@
|
||||
a0a268746b849e72488e8608dc7529251d735d933091a22e5f93d38662ef7534
|
||||
d31a690f913188ddcf07b749ff1ed53974efbd88f60dacca614c62e22165ddc7
|
||||
|
||||
@@ -158,3 +158,4 @@ E:\GIT\forkmessager\apps\server-net\src\Vortex.Host\bin\Debug\net10.0\System.Ide
|
||||
E:\GIT\forkmessager\apps\server-net\src\Vortex.Host\bin\Debug\net10.0\Microsoft.AspNetCore.Authentication.JwtBearer.dll
|
||||
E:\GIT\forkmessager\apps\server-net\src\Vortex.Host\bin\Debug\net10.0\Microsoft.IdentityModel.Protocols.dll
|
||||
E:\GIT\forkmessager\apps\server-net\src\Vortex.Host\bin\Debug\net10.0\Microsoft.IdentityModel.Protocols.OpenIdConnect.dll
|
||||
E:\GIT\forkmessager\apps\server-net\src\Vortex.Host\bin\Debug\net10.0\SixLabors.ImageSharp.dll
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"GlobalPropertiesHash":"2XkPbXFkjVRjCUBTJbQq8CDd7pjsUOS1j3gPHS73xDA=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["uOUIBvk7jdHp7c93RIuECwNykpOmiZnVmQxETsngeR0=","ToV71GGCtcAptkaGqQYXeNtDQnAJEljvYAkAKnIRZ\u002Bs=","2fjU\u002BjCBiHEuKj2uQgoWti0oZXkpAdICFty/O9cJSyc=","aTLUi985DoL1wb7QmuhiHoO7J3hi30WEVAUbYVkXxss=","GsJcYsaPdTiZszOuaIQXvBoHa2G\u002B8z3ZDvEk9/eI47g="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||
{"GlobalPropertiesHash":"2XkPbXFkjVRjCUBTJbQq8CDd7pjsUOS1j3gPHS73xDA=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["uOUIBvk7jdHp7c93RIuECwNykpOmiZnVmQxETsngeR0=","ToV71GGCtcAptkaGqQYXeNtDQnAJEljvYAkAKnIRZ\u002Bs=","2fjU\u002BjCBiHEuKj2uQgoWti0oZXkpAdICFty/O9cJSyc=","aTLUi985DoL1wb7QmuhiHoO7J3hi30WEVAUbYVkXxss=","zR9GPKJ4P6VsVoHQ2DUOzlUgEbOZ6Jtpw3dF8z1T3G8="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||
@@ -1 +1 @@
|
||||
{"GlobalPropertiesHash":"2L4HBRt/ChGO9rohi3wtSDqo95X47fme5m8OHgJIZL0=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["uOUIBvk7jdHp7c93RIuECwNykpOmiZnVmQxETsngeR0=","ToV71GGCtcAptkaGqQYXeNtDQnAJEljvYAkAKnIRZ\u002Bs=","2fjU\u002BjCBiHEuKj2uQgoWti0oZXkpAdICFty/O9cJSyc=","aTLUi985DoL1wb7QmuhiHoO7J3hi30WEVAUbYVkXxss=","GsJcYsaPdTiZszOuaIQXvBoHa2G\u002B8z3ZDvEk9/eI47g="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||
{"GlobalPropertiesHash":"2L4HBRt/ChGO9rohi3wtSDqo95X47fme5m8OHgJIZL0=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["uOUIBvk7jdHp7c93RIuECwNykpOmiZnVmQxETsngeR0=","ToV71GGCtcAptkaGqQYXeNtDQnAJEljvYAkAKnIRZ\u002Bs=","2fjU\u002BjCBiHEuKj2uQgoWti0oZXkpAdICFty/O9cJSyc=","aTLUi985DoL1wb7QmuhiHoO7J3hi30WEVAUbYVkXxss=","zR9GPKJ4P6VsVoHQ2DUOzlUgEbOZ6Jtpw3dF8z1T3G8="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||
@@ -1994,6 +1994,10 @@
|
||||
"target": "Package",
|
||||
"version": "[10.0.0, )"
|
||||
},
|
||||
"SixLabors.ImageSharp": {
|
||||
"target": "Package",
|
||||
"version": "[3.1.12, )"
|
||||
},
|
||||
"Swashbuckle.AspNetCore": {
|
||||
"target": "Package",
|
||||
"version": "[10.1.5, )"
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
<Import Project="$(NuGetPackageRoot)microsoft.entityframeworkcore\10.0.4\buildTransitive\net10.0\Microsoft.EntityFrameworkCore.props" Condition="Exists('$(NuGetPackageRoot)microsoft.entityframeworkcore\10.0.4\buildTransitive\net10.0\Microsoft.EntityFrameworkCore.props')" />
|
||||
<Import Project="$(NuGetPackageRoot)microsoft.extensions.apidescription.server\10.0.0\build\Microsoft.Extensions.ApiDescription.Server.props" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.apidescription.server\10.0.0\build\Microsoft.Extensions.ApiDescription.Server.props')" />
|
||||
<Import Project="$(NuGetPackageRoot)swashbuckle.aspnetcore\10.1.5\build\Swashbuckle.AspNetCore.props" Condition="Exists('$(NuGetPackageRoot)swashbuckle.aspnetcore\10.1.5\build\Swashbuckle.AspNetCore.props')" />
|
||||
<Import Project="$(NuGetPackageRoot)sixlabors.imagesharp\3.1.12\build\SixLabors.ImageSharp.props" Condition="Exists('$(NuGetPackageRoot)sixlabors.imagesharp\3.1.12\build\SixLabors.ImageSharp.props')" />
|
||||
<Import Project="$(NuGetPackageRoot)microsoft.codeanalysis.analyzers\3.11.0\buildTransitive\Microsoft.CodeAnalysis.Analyzers.props" Condition="Exists('$(NuGetPackageRoot)microsoft.codeanalysis.analyzers\3.11.0\buildTransitive\Microsoft.CodeAnalysis.Analyzers.props')" />
|
||||
<Import Project="$(NuGetPackageRoot)microsoft.entityframeworkcore.design\10.0.4\build\net10.0\Microsoft.EntityFrameworkCore.Design.props" Condition="Exists('$(NuGetPackageRoot)microsoft.entityframeworkcore.design\10.0.4\build\net10.0\Microsoft.EntityFrameworkCore.Design.props')" />
|
||||
</ImportGroup>
|
||||
|
||||
@@ -1022,6 +1022,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"SixLabors.ImageSharp/3.1.12": {
|
||||
"type": "package",
|
||||
"compile": {
|
||||
"lib/net6.0/SixLabors.ImageSharp.dll": {
|
||||
"related": ".xml"
|
||||
}
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net6.0/SixLabors.ImageSharp.dll": {
|
||||
"related": ".xml"
|
||||
}
|
||||
},
|
||||
"build": {
|
||||
"build/SixLabors.ImageSharp.props": {}
|
||||
}
|
||||
},
|
||||
"Swashbuckle.AspNetCore/10.1.5": {
|
||||
"type": "package",
|
||||
"dependencies": {
|
||||
@@ -3641,6 +3657,22 @@
|
||||
"postgresql.png"
|
||||
]
|
||||
},
|
||||
"SixLabors.ImageSharp/3.1.12": {
|
||||
"sha512": "iAg6zifihXEFS/t7fiHhZBGAdCp3FavsF4i2ZIDp0JfeYeDVzvmlbY1CNhhIKimaIzrzSi5M/NBFcWvZT2rB/A==",
|
||||
"type": "package",
|
||||
"path": "sixlabors.imagesharp/3.1.12",
|
||||
"files": [
|
||||
".nupkg.metadata",
|
||||
".signature.p7s",
|
||||
"LICENSE",
|
||||
"build/SixLabors.ImageSharp.props",
|
||||
"lib/net6.0/SixLabors.ImageSharp.dll",
|
||||
"lib/net6.0/SixLabors.ImageSharp.xml",
|
||||
"sixlabors.imagesharp.128.png",
|
||||
"sixlabors.imagesharp.3.1.12.nupkg.sha512",
|
||||
"sixlabors.imagesharp.nuspec"
|
||||
]
|
||||
},
|
||||
"Swashbuckle.AspNetCore/10.1.5": {
|
||||
"sha512": "/eNk9z/8quXhDX14o3XLbwAX/84uIWSbiUD7cI/UrQnoBMOiyAtzKxNEJUtf/TyxjFpcXxE9FAfLvtbNpxHBSg==",
|
||||
"type": "package",
|
||||
@@ -3962,6 +3994,7 @@
|
||||
"Microsoft.EntityFrameworkCore.Design >= 10.0.4",
|
||||
"Microsoft.IdentityModel.Tokens >= 8.16.0",
|
||||
"Npgsql.EntityFrameworkCore.PostgreSQL >= 10.0.0",
|
||||
"SixLabors.ImageSharp >= 3.1.12",
|
||||
"Swashbuckle.AspNetCore >= 10.1.5",
|
||||
"System.IdentityModel.Tokens.Jwt >= 8.16.0",
|
||||
"Vortex.Modules.Chats >= 1.0.0",
|
||||
@@ -4068,6 +4101,10 @@
|
||||
"target": "Package",
|
||||
"version": "[10.0.0, )"
|
||||
},
|
||||
"SixLabors.ImageSharp": {
|
||||
"target": "Package",
|
||||
"version": "[3.1.12, )"
|
||||
},
|
||||
"Swashbuckle.AspNetCore": {
|
||||
"target": "Package",
|
||||
"version": "[10.1.5, )"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "Lr9L1t57HIQ=",
|
||||
"dgSpecHash": "ghRzA2bAmsQ=",
|
||||
"success": true,
|
||||
"projectFilePath": "E:\\GIT\\forkmessager\\apps\\server-net\\src\\Vortex.Host\\Vortex.Host.csproj",
|
||||
"expectedPackageFiles": [
|
||||
@@ -53,6 +53,7 @@
|
||||
"C:\\Users\\HomePC\\.nuget\\packages\\newtonsoft.json\\13.0.3\\newtonsoft.json.13.0.3.nupkg.sha512",
|
||||
"C:\\Users\\HomePC\\.nuget\\packages\\npgsql\\10.0.0\\npgsql.10.0.0.nupkg.sha512",
|
||||
"C:\\Users\\HomePC\\.nuget\\packages\\npgsql.entityframeworkcore.postgresql\\10.0.0\\npgsql.entityframeworkcore.postgresql.10.0.0.nupkg.sha512",
|
||||
"C:\\Users\\HomePC\\.nuget\\packages\\sixlabors.imagesharp\\3.1.12\\sixlabors.imagesharp.3.1.12.nupkg.sha512",
|
||||
"C:\\Users\\HomePC\\.nuget\\packages\\swashbuckle.aspnetcore\\10.1.5\\swashbuckle.aspnetcore.10.1.5.nupkg.sha512",
|
||||
"C:\\Users\\HomePC\\.nuget\\packages\\swashbuckle.aspnetcore.swagger\\10.1.5\\swashbuckle.aspnetcore.swagger.10.1.5.nupkg.sha512",
|
||||
"C:\\Users\\HomePC\\.nuget\\packages\\swashbuckle.aspnetcore.swaggergen\\10.1.5\\swashbuckle.aspnetcore.swaggergen.10.1.5.nupkg.sha512",
|
||||
|
||||
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 228 KiB |