diff --git a/Govor.API.Tests/IntegrationTests/Controllers/FriendsRequestQueryControllerTests.cs b/Govor.API.Tests/IntegrationTests/Controllers/FriendsRequestQueryControllerTests.cs index 8b6c204..41b0c58 100644 --- a/Govor.API.Tests/IntegrationTests/Controllers/FriendsRequestQueryControllerTests.cs +++ b/Govor.API.Tests/IntegrationTests/Controllers/FriendsRequestQueryControllerTests.cs @@ -48,7 +48,10 @@ public class FriendsRequestQueryControllerTests var currentId = _fixture.Create(); var friendships = _fixture.CreateMany().ToList(); var dtos = friendships.Select(f => new FriendshipDto() - { AddresseeId = f.AddresseeId, RequesterId = f.RequesterId }).ToList(); + { + AddresseeId = f.AddresseeId, + RequesterId = f.RequesterId + }).ToList(); _currentUserServiceMock.Setup(c => c.GetCurrentUserId()) .Returns(currentId); @@ -125,7 +128,10 @@ public class FriendsRequestQueryControllerTests var currentId = _fixture.Create(); var friendships = _fixture.CreateMany().ToList(); var dtos = friendships.Select(f => new FriendshipDto() - { AddresseeId = f.AddresseeId, RequesterId = f.RequesterId }).ToList(); + { + AddresseeId = f.AddresseeId, + RequesterId = f.RequesterId + }).ToList(); _currentUserServiceMock.Setup(c => c.GetCurrentUserId()) .Returns(currentId); diff --git a/Govor.API.Tests/IntegrationTests/Hubs/FriendsHubTests.cs b/Govor.API.Tests/IntegrationTests/Hubs/FriendsHubTests.cs index 9f88695..57010f7 100644 --- a/Govor.API.Tests/IntegrationTests/Hubs/FriendsHubTests.cs +++ b/Govor.API.Tests/IntegrationTests/Hubs/FriendsHubTests.cs @@ -1,8 +1,12 @@ +using AutoFixture; +using AutoMapper; using Govor.API.Hubs; using Govor.Application.Exceptions.FriendsService; using Govor.Application.Interfaces.Friends; using Govor.Application.Interfaces.Infrastructure.Extensions; +using Govor.Contracts.DTOs; using Govor.Contracts.Responses.SignalR; +using Govor.Core.Models; using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.Logging; using Moq; @@ -17,26 +21,34 @@ public class FriendsHubTests private Mock _clientsMock = null!; private Mock _clientProxyMock = null!; private Mock> _loggerMock = null!; + private Mock _mapperMock = null!; private FriendsHub _hub = null!; + private Fixture _fixture; private readonly Guid _userId = Guid.NewGuid(); private readonly Guid _targetUserId = Guid.NewGuid(); [SetUp] public void Setup() { + _fixture = new Fixture(); + _fixture.Behaviors.OfType().ToList().ForEach(b => _fixture.Behaviors.Remove(b)); + _fixture.Behaviors.Add(new OmitOnRecursionBehavior()); + _friendRequestServiceMock = new Mock(); _currentUserServiceMock = new Mock(); _clientsMock = new Mock(); _clientProxyMock = new Mock(); _loggerMock = new Mock>(); - + _mapperMock = new Mock(); + _currentUserServiceMock.Setup(x => x.GetCurrentUserId()).Returns(_userId); - _clientsMock.Setup(c => c.User(It.IsAny())).Returns(_clientProxyMock.Object); - + _clientsMock.Setup(c => c.Group(It.IsAny())).Returns(_clientProxyMock.Object); + _hub = new FriendsHub( _friendRequestServiceMock.Object, _currentUserServiceMock.Object, - _loggerMock.Object) + _loggerMock.Object, + _mapperMock.Object) { Clients = _clientsMock.Object }; @@ -46,6 +58,26 @@ public class FriendsHubTests [Test] public async Task SendRequest_ShouldReturnCreated_WhenSuccess() { + var friendship = _fixture.Build() + .With(f => f.Status, FriendshipStatus.Pending) + .With(f => f.RequesterId, _userId) + .With(f =>f.AddresseeId, _targetUserId) + .Create(); + + var dto = new FriendshipDto() + { + Id = friendship.Id, + Status = FriendshipStatus.Accepted, + AddresseeId = friendship.AddresseeId, + RequesterId = friendship.RequesterId, + }; + + _mapperMock.Setup(f => f.Map(friendship)) + .Returns(dto); + + _friendRequestServiceMock.Setup(f => f.SendAsync(_userId, _targetUserId)) + .ReturnsAsync(friendship); + // Act var result = await _hub.SendRequest(_targetUserId); @@ -57,7 +89,7 @@ public class FriendsHubTests _clientProxyMock.Verify(c => c.SendCoreAsync( "FriendRequestReceived", - It.Is(o => o[0]!.Equals(_userId)), + It.Is(o => o[0]!.Equals(dto)), default), Times.Once); } @@ -129,19 +161,35 @@ public class FriendsHubTests public async Task AcceptRequest_ShouldReturnOk_WhenSuccess() { // Arrange - var friendshipId = Guid.NewGuid(); + var friendship = _fixture.Build() + .With(f => f.Status, FriendshipStatus.Pending) + .Create(); + var dto = new FriendshipDto() + { + Id = friendship.Id, + Status = FriendshipStatus.Accepted, + AddresseeId = friendship.AddresseeId, + RequesterId = friendship.RequesterId + }; + + _mapperMock.Setup(f => f.Map(friendship)) + .Returns(dto); + + _friendRequestServiceMock.Setup(f => f.AcceptAsync(friendship.Id, _userId)) + .ReturnsAsync(friendship); // Act - var result = await _hub.AcceptRequest(friendshipId); + var result = await _hub.AcceptRequest(friendship.Id); // Assert Assert.That(result.Status, Is.EqualTo(HubResultStatus.Success)); - _friendRequestServiceMock.Verify(s => s.AcceptAsync(friendshipId, _userId), Times.Once); + _friendRequestServiceMock.Verify(s => s.AcceptAsync(friendship.Id, _userId), Times.Once); _clientProxyMock.Verify(c => c.SendCoreAsync( "FriendRequestAccepted", - It.Is(o => o[0]!.Equals(friendshipId)), + It.Is(o => o.Length == 1 && o[0] == dto), default), Times.Once); + } [Test] @@ -201,18 +249,33 @@ public class FriendsHubTests public async Task RejectRequest_ShouldReturnOk_WhenSuccess() { // Arrange - var friendshipId = Guid.NewGuid(); - + var friendship = _fixture.Build() + .With(f => f.Status, FriendshipStatus.Pending) + .Create(); + var dto = new FriendshipDto() + { + Id = friendship.Id, + Status = FriendshipStatus.Accepted, + AddresseeId = friendship.AddresseeId, + RequesterId = friendship.RequesterId + }; + + _mapperMock.Setup(f => f.Map(friendship)) + .Returns(dto); + + _friendRequestServiceMock.Setup(f => f.RejectAsync(friendship.Id, _userId)) + .ReturnsAsync(friendship); + // Act - var result = await _hub.RejectRequest(friendshipId); + var result = await _hub.RejectRequest(friendship.Id); // Assert Assert.That(result.Status, Is.EqualTo(HubResultStatus.Success)); - _friendRequestServiceMock.Verify(s => s.RejectAsync(friendshipId, _userId), Times.Once); + _friendRequestServiceMock.Verify(s => s.RejectAsync(friendship.Id, _userId), Times.Once); _clientProxyMock.Verify(c => c.SendCoreAsync( "FriendRequestRejected", - It.Is(o => o[0]!.Equals(friendshipId)), + It.Is(o => o[0]!.Equals(dto)), default), Times.Once); } diff --git a/Govor.API/Controllers/AdminStuff/FriendshipsController.cs b/Govor.API/Controllers/AdminStuff/FriendshipsController.cs index 502abf4..94b5225 100644 --- a/Govor.API/Controllers/AdminStuff/FriendshipsController.cs +++ b/Govor.API/Controllers/AdminStuff/FriendshipsController.cs @@ -29,7 +29,7 @@ public class FriendshipsController : Controller { _logger.LogInformation("Get all friendships by administrator"); var result = await _friendshipsRepository.GetAllAsync(); - return Ok(BuildFriendshipDtos(result)); + return Ok(result); } catch (NotFoundException ex) { @@ -53,7 +53,7 @@ public class FriendshipsController : Controller { _logger.LogInformation($"Get user's {userId} all friendships by administrator"); var result = await _friendshipsRepository.FindByUserIdAsync(userId); - return Ok(BuildFriendshipDtos(result)); + return Ok(result); } catch (NotFoundByKeyException ex) { @@ -89,21 +89,4 @@ public class FriendshipsController : Controller return StatusCode(500, new { error = "Internal server error." }); } } - - private List BuildUserDtos(IEnumerable users) => users.Select(user => new UserDto - { - Id = user.Id, - Username = user.Username, - Description = user.Description, - WasOnline = user.WasOnline, - IconId = user.IconId - }).ToList(); - - private List BuildFriendshipDtos(IEnumerable friendships) => friendships.Select(f => new FriendshipDto - { - Id = f.Id, - Status = f.Status, - AddresseeId = f.AddresseeId, - RequesterId = f.RequesterId, - }).ToList(); } \ No newline at end of file diff --git a/Govor.API/Controllers/Friends/FriendsRequestQueryController.cs b/Govor.API/Controllers/Friends/FriendsRequestQueryController.cs index 77deda3..f3f8bda 100644 --- a/Govor.API/Controllers/Friends/FriendsRequestQueryController.cs +++ b/Govor.API/Controllers/Friends/FriendsRequestQueryController.cs @@ -56,7 +56,11 @@ public class FriendsRequestQueryController : Controller { try { - var result = await _friendsService.GetResponsesAsync(_currentUserService.GetCurrentUserId()); + var userId = _currentUserService.GetCurrentUserId(); + + _logger.LogInformation("Getting responses by user {userId}", userId); + + var result = await _friendsService.GetResponsesAsync(userId); var response = _mapper.Map>(result); return Ok(response); diff --git a/Govor.API/Controllers/MediaController.cs b/Govor.API/Controllers/MediaController.cs index b7f00d8..d842430 100644 --- a/Govor.API/Controllers/MediaController.cs +++ b/Govor.API/Controllers/MediaController.cs @@ -1,4 +1,5 @@ using Govor.Application.Interfaces; +using Govor.Application.Interfaces.Infrastructure.Extensions; using Govor.Application.Interfaces.Medias; using Govor.Contracts.Requests; using Govor.Core.Models; @@ -15,27 +16,67 @@ public class MediaController : Controller { private readonly ILogger _logger; private readonly IMediaService _mediaService; - - public MediaController(ILogger logger, IMediaService mediaService) + private readonly ICurrentUserService _currentUserService; + + public MediaController( + ILogger logger, + IMediaService mediaService, + ICurrentUserService currentUserService) { _logger = logger; _mediaService = mediaService; + _currentUserService = currentUserService; } [HttpPost("upload")] - [RequestSizeLimit(100_000_000)]// ~100MB + [RequestSizeLimit(100_000_000)] // ~100MB public async Task Upload([FromForm] MediaUploadRequest request) { try { - var result = await _mediaService.UploadMediaAsync(new Media(request.Data, request.FileName, request.Type, - request.MimeType, request.EncryptedKey)); - - return Ok(result); + if (!ModelState.IsValid) + return BadRequest(ModelState); + + // Чтение байт из IFormFile + using var memoryStream = new MemoryStream(); + await request.Data.CopyToAsync(memoryStream); + byte[] fileBytes = memoryStream.ToArray(); + + var media = new Media( + _currentUserService.GetCurrentUserId(), + DateTime.UtcNow, + fileBytes, + request.FileName, + request.Type, + request.MimeType, + request.EncryptedKey + ); + + var result = await _mediaService.UploadMediaAsync(media); + return Ok(result); } catch (Exception ex) { - return StatusCode(500, ex); + _logger.LogError(ex, "Error uploading media"); + return StatusCode(500, "Internal server error"); } } -} \ No newline at end of file + + [HttpGet("download/{id}")] + public async Task Download(Guid id) + { + try + { + if (!ModelState.IsValid) + return BadRequest(ModelState); + + var media = await _mediaService.GetMediaByIdAsync(id); + return File(media.Data, media.MineType, media.FileName); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error downloading media"); + return StatusCode(500, "Internal server error"); + } + } +} diff --git a/Govor.API/Extensions/ConfigurationProgramExtensions.cs b/Govor.API/Extensions/ConfigurationProgramExtensions.cs index c0b5812..2689d8a 100644 --- a/Govor.API/Extensions/ConfigurationProgramExtensions.cs +++ b/Govor.API/Extensions/ConfigurationProgramExtensions.cs @@ -7,6 +7,7 @@ using Govor.Application.Interfaces; using Govor.Application.Interfaces.Authentication; using Govor.Application.Interfaces.Friends; using Govor.Application.Interfaces.Infrastructure.Extensions; +using Govor.Application.Interfaces.Medias; using Govor.Application.Interfaces.Messages; using Govor.Application.Services; using Govor.Application.Services.Authentication; @@ -65,6 +66,7 @@ public static class ConfigurationProgramExtensions services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); // Auto Mapper services.AddAutoMapper(typeof(MappingProfile)); diff --git a/Govor.API/Hubs/ChatsHub.cs b/Govor.API/Hubs/ChatsHub.cs index 5db3c95..4869472 100644 --- a/Govor.API/Hubs/ChatsHub.cs +++ b/Govor.API/Hubs/ChatsHub.cs @@ -198,11 +198,25 @@ public class ChatsHub : Hub { var result = await _messageCommandService.EditMessageAsync(editMessageParam); - if (!result.IsSuccess) + if (!result.IsSuccess || result.OriginalMessage == null) return LogAndError(editor, request.MessageId, "Edit message error", result.Exception); - return HubResult.Ok(); + var response = new MessageEditResponse() + { + MessageId = result.messageId, + EditorId = editor, + RecipientId = result.OriginalMessage.RecipientId, + RecipientType = result.OriginalMessage.RecipientType, + NewEncryptedContent = request.NewEncryptedContent, + EditedAt = editMessageParam.EditedAt, + }; + + await NotifyClientsAboutEdit(response); + + _logger.LogInformation("Message {MessageId} edited successfully by {editor}", request.MessageId, editor); + + return HubResult.Ok(response); } catch (UnauthorizedAccessException ex) { @@ -259,6 +273,20 @@ public class ChatsHub : Hub } } + private async Task NotifyClientsAboutEdit(MessageEditResponse response) + { + if (response.RecipientType == RecipientType.User) + { + await Clients.Group(response.EditorId.ToString()).SendAsync("MessageEdit", response); + if (response.EditorId != response.RecipientId) + await Clients.Group(response.RecipientId.ToString()).SendAsync("MessageEdit", response); + } + else + { + await Clients.Group($"group_{response.RecipientId}").SendAsync("MessageEdit", response); + } + } + // Logging helpers private HubResult LogAndError(Guid userId, Guid targetId, string message, Exception ex) { diff --git a/Govor.API/Hubs/FriendsHub.cs b/Govor.API/Hubs/FriendsHub.cs index 1e9e309..b252080 100644 --- a/Govor.API/Hubs/FriendsHub.cs +++ b/Govor.API/Hubs/FriendsHub.cs @@ -1,6 +1,9 @@ +using System.ComponentModel.DataAnnotations; +using AutoMapper; using Govor.Application.Exceptions.FriendsService; using Govor.Application.Interfaces.Friends; using Govor.Application.Interfaces.Infrastructure.Extensions; +using Govor.Contracts.DTOs; using Govor.Contracts.Responses.SignalR; using Microsoft.AspNetCore.SignalR; @@ -11,12 +14,17 @@ public class FriendsHub : Hub private readonly ILogger _logger; private readonly IFriendRequestCommandService _friendRequestService; private readonly ICurrentUserService _currentUserService; + private readonly IMapper _mapper; - public FriendsHub(IFriendRequestCommandService friendRequestService, ICurrentUserService currentUserService, ILogger logger) + public FriendsHub(IFriendRequestCommandService friendRequestService, + ICurrentUserService currentUserService, + ILogger logger, + IMapper mapper) { _friendRequestService = friendRequestService; _currentUserService = currentUserService; _logger = logger; + _mapper = mapper; } public override async Task OnConnectedAsync() @@ -70,9 +78,10 @@ public class FriendsHub : Hub try { var userId = _currentUserService.GetCurrentUserId(); - await _friendRequestService.SendAsync(userId, targetUserId); - await Clients.User(targetUserId.ToString()) - .SendAsync("FriendRequestReceived", userId); + var friendship = await _friendRequestService.SendAsync(userId, targetUserId); + + await Clients.Group(targetUserId.ToString()) + .SendAsync("FriendRequestReceived", _mapper.Map(friendship)); _logger.LogInformation($"Friend request received for user {targetUserId} from {userId}."); return HubResult.Created(); @@ -104,9 +113,9 @@ public class FriendsHub : Hub try { var userId = _currentUserService.GetCurrentUserId(); - await _friendRequestService.AcceptAsync(friendshipId, userId); - await Clients.User(userId.ToString()) - .SendAsync("FriendRequestAccepted", friendshipId); + var friendship = await _friendRequestService.AcceptAsync(friendshipId, userId); + await Clients.Group(userId.ToString()) + .SendAsync("FriendRequestAccepted", _mapper.Map(friendship)); _logger.LogInformation($"Friend request accepted for user {userId} from {userId}."); return HubResult.Ok(); @@ -133,9 +142,9 @@ public class FriendsHub : Hub try { var userId = _currentUserService.GetCurrentUserId(); - await _friendRequestService.RejectAsync(friendshipId, userId); - await Clients.User(userId.ToString()) - .SendAsync("FriendRequestRejected", friendshipId); + var friendship = await _friendRequestService.RejectAsync(friendshipId, userId); + await Clients.Group(userId.ToString()) + .SendAsync("FriendRequestRejected", _mapper.Map(friendship)); _logger.LogInformation($"Friend request rejected for user {userId} from {userId}."); return HubResult.Ok(); diff --git a/Govor.Application/Interfaces/Friends/IFriendRequestCommandService.cs b/Govor.Application/Interfaces/Friends/IFriendRequestCommandService.cs index 21f9d24..3d3c4e4 100644 --- a/Govor.Application/Interfaces/Friends/IFriendRequestCommandService.cs +++ b/Govor.Application/Interfaces/Friends/IFriendRequestCommandService.cs @@ -1,8 +1,10 @@ +using Govor.Core.Models; + namespace Govor.Application.Interfaces.Friends; public interface IFriendRequestCommandService { - Task SendAsync(Guid fromUserId, Guid toUserId); - Task AcceptAsync(Guid requestId, Guid currentUserId); - Task RejectAsync(Guid requestId, Guid currentUserId); + Task SendAsync(Guid fromUserId, Guid toUserId); + Task AcceptAsync(Guid requestId, Guid currentUserId); + Task RejectAsync(Guid requestId, Guid currentUserId); } diff --git a/Govor.Application/Interfaces/Medias/IMediaService.cs b/Govor.Application/Interfaces/Medias/IMediaService.cs index 6b0e3b3..48a8232 100644 --- a/Govor.Application/Interfaces/Medias/IMediaService.cs +++ b/Govor.Application/Interfaces/Medias/IMediaService.cs @@ -6,9 +6,16 @@ public interface IMediaService { public Task UploadMediaAsync(Media file); public Task DeleteMediaAsync(Guid fileId); - public Task GetMediaAsync(string url); + public Task GetMediaByUrlAsync(string url); + public Task GetMediaByIdAsync(Guid mediaId); } -public record Media(byte[] Data, string FileName, MediaType Type, string MineType, string EncryptedKey); +public record Media(Guid UploaderId, + DateTime UploadedOn, + byte[] Data, + string FileName, + MediaType Type, + string MineType, + string EncryptedKey); public record MediaUploadResult(Guid? MediaId, string Url); \ No newline at end of file diff --git a/Govor.Application/Services/Friends/FriendRequestCommandService.cs b/Govor.Application/Services/Friends/FriendRequestCommandService.cs index e4e8780..3e6b4bb 100644 --- a/Govor.Application/Services/Friends/FriendRequestCommandService.cs +++ b/Govor.Application/Services/Friends/FriendRequestCommandService.cs @@ -15,7 +15,7 @@ public class FriendRequestCommandService : IFriendRequestCommandService _friendshipsRepository = friendshipsRepository; } - public async Task SendAsync(Guid fromUserId, Guid toUserId) + public async Task SendAsync(Guid fromUserId, Guid toUserId) { if (fromUserId == toUserId) throw new InvalidOperationException("Cannot send a request to self user"); @@ -23,16 +23,20 @@ public class FriendRequestCommandService : IFriendRequestCommandService if (_friendshipsRepository.Exist(fromUserId, toUserId)) throw new RequestAlreadySentException(fromUserId, toUserId); - await _friendshipsRepository.AddAsync(new Friendship + var friendship = new Friendship { Id = Guid.NewGuid(), RequesterId = fromUserId, AddresseeId = toUserId, Status = FriendshipStatus.Pending - }); + }; + + await _friendshipsRepository.AddAsync(friendship); + + return friendship; } - public async Task AcceptAsync(Guid requestId, Guid currentUserId) + public async Task AcceptAsync(Guid requestId, Guid currentUserId) { try { @@ -46,6 +50,8 @@ public class FriendRequestCommandService : IFriendRequestCommandService friendship.Status = FriendshipStatus.Accepted; await _friendshipsRepository.UpdateAsync(friendship); + + return friendship; } catch (NotFoundByKeyException ex) { @@ -53,7 +59,7 @@ public class FriendRequestCommandService : IFriendRequestCommandService } } - public async Task RejectAsync(Guid requestId, Guid currentUserId) + public async Task RejectAsync(Guid requestId, Guid currentUserId) { try { @@ -67,6 +73,7 @@ public class FriendRequestCommandService : IFriendRequestCommandService friendship.Status = FriendshipStatus.Rejected; await _friendshipsRepository.UpdateAsync(friendship); + return friendship; } catch (NotFoundByKeyException ex) { diff --git a/Govor.Application/Services/Messages/MediaService.cs b/Govor.Application/Services/Messages/MediaService.cs index 46df591..21bba28 100644 --- a/Govor.Application/Services/Messages/MediaService.cs +++ b/Govor.Application/Services/Messages/MediaService.cs @@ -1,20 +1,53 @@ using Govor.Application.Interfaces; using Govor.Application.Interfaces.Medias; +using Govor.Core.Models; +using Govor.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; namespace Govor.Application.Services.Messages; public class MediaService : IMediaService { + private ILogger _logger; private IStorageService _storageService; + private GovorDbContext _dbContext; - public MediaService(IStorageService storageService) + public MediaService(IStorageService storageService, GovorDbContext dbContext, ILogger logger) { _storageService = storageService; + _dbContext = dbContext; + _logger = logger; } - public Task UploadMediaAsync(Media file) + public async Task UploadMediaAsync(Media file) { - throw new NotImplementedException(); + try + { + var url = await _storageService.SaveAsync(file.Data, file.FileName); + + var mediaId = Guid.NewGuid(); + + _dbContext.MediaFiles.Add(new MediaFile() + { + Id = mediaId, + UploaderId = file.UploaderId, + DateCreated = file.UploadedOn, + MediaType = file.Type, + MineType = file.MineType, + Url = url + }); + + await _dbContext.SaveChangesAsync(); + + _logger.LogInformation($"Media uploaded: {url} with id: {mediaId} by {file.UploaderId}"); + + return new MediaUploadResult(mediaId, url); + } + catch (ArgumentException ex) + { + throw new InvalidOperationException($"An error occured while uploading the media file: {ex.Message}"); + } } public Task DeleteMediaAsync(Guid fileId) @@ -22,8 +55,46 @@ public class MediaService : IMediaService throw new NotImplementedException(); } - public Task GetMediaAsync(string url) + public Task GetMediaByUrlAsync(string url) { throw new NotImplementedException(); } + + public async Task GetMediaByIdAsync(Guid mediaId) + { + try + { + var mediaFile = await _dbContext.MediaFiles + .AsNoTracking() + .FirstOrDefaultAsync(x => x.Id == mediaId) + ?? throw new KeyNotFoundException("No media found"); + + // Загрузить бинарные данные из хранилища + Stream dataStream = await _storageService.LoadAsync(mediaFile.Url); + + // Считать поток в byte[] + using var memoryStream = new MemoryStream(); + await dataStream.CopyToAsync(memoryStream); + var contentBytes = memoryStream.ToArray(); + + _logger.LogInformation($"Media found: {mediaFile.MediaType} with id: {mediaFile.Id} and url: {mediaFile.Url}"); + + // Вернуть объект Media + return new Media( + mediaFile.UploaderId, + mediaFile.DateCreated, + contentBytes, + string.Empty, + mediaFile.MediaType, + mediaFile.MineType, + string.Empty + ); + } + catch (FileNotFoundException ex) + { + _logger.LogWarning(ex, "Media file not found on storage."); + throw; + } + } + } \ No newline at end of file diff --git a/Govor.Console/Commands/AcceptFriendRequestCommand.cs b/Govor.Console/Commands/AcceptFriendRequestCommand.cs deleted file mode 100644 index a81be53..0000000 --- a/Govor.Console/Commands/AcceptFriendRequestCommand.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.SignalR.Client; - -namespace Govor.ConsoleClient.Commands -{ - public class AcceptFriendRequestCommand : BaseCommand - { - public override async Task ExecuteAsync(string? argument) - { - if (!EnsureLoggedIn() || !EnsureHubConnection()) return; - - Guid friendshipId; - if (string.IsNullOrWhiteSpace(argument) || !Guid.TryParse(argument, out friendshipId)) - { - Console.Write("Введите ID заявки, которую хотите принять: "); - var input = Console.ReadLine(); - if (string.IsNullOrWhiteSpace(input) || !Guid.TryParse(input, out friendshipId)) - { - Console.WriteLine("[Ошибка] Неверный или пустой ID заявки."); - return; - } - } - - try - { - // API uses Hub for this: AcceptFriendRequest(Guid friendshipId) - await HubConnection.InvokeAsync("AcceptRequest", friendshipId); - Console.WriteLine("Заявка в друзья принята."); - } - catch (Exception ex) - { - Console.WriteLine($"[Ошибка принятия заявки] {ex.Message}"); - } - } - - public override string GetHelp() - { - return "/accept [ID_заявки] - Принять входящую заявку в друзья. Если ID не указан, запросит ввод."; - } - } -} diff --git a/Govor.Console/Commands/AdminListAllFriendshipsCommand.cs b/Govor.Console/Commands/AdminListAllFriendshipsCommand.cs deleted file mode 100644 index 226c547..0000000 --- a/Govor.Console/Commands/AdminListAllFriendshipsCommand.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Net.Http.Json; -using System.Text.Json; -using System.Threading.Tasks; -using Govor.Contracts.DTOs; // Required for FriendshipDto - -namespace Govor.ConsoleClient.Commands -{ - public class AdminListAllFriendshipsCommand : BaseCommand - { - public override async Task ExecuteAsync(string? argument) - { - if (!EnsureLoggedIn()) return; - // Consider adding an admin role check here if possible, - // though the API itself is protected by [Authorize(Roles = "Admin")] - - Console.WriteLine("Получение всех дружеских связей (только для администраторов)..."); - try - { - var json = await HttpClientService.GetAsync("api/admin/Friendships"); - - var friendships = JsonSerializer.Deserialize>(json, new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true - }); - - if (friendships != null && friendships.Any()) - { - Console.WriteLine("Все дружеские связи в системе:"); - foreach (var f in friendships) - { - Console.WriteLine($"- ID: {f.Id}, Пользователь1: {f.RequesterId}, Пользователь2: {f.AddresseeId}, Статус: {f.Status}"); - } - } - else - { - Console.WriteLine("Дружеские связи не найдены в системе."); - } - } - catch (HttpRequestException httpEx) when (httpEx.StatusCode == System.Net.HttpStatusCode.Forbidden) - { - Console.WriteLine("[Ошибка] Доступ запрещен. Эта команда только для администраторов."); - } - catch (Exception ex) - { - Console.WriteLine($"[Ошибка] {ex.Message}"); - } - } - - public override string GetHelp() - { - return "/adminlistallfs - (Админ) Показать все дружеские связи в системе."; - } - } -} diff --git a/Govor.Console/Commands/AdminListUserFriendshipsCommand.cs b/Govor.Console/Commands/AdminListUserFriendshipsCommand.cs deleted file mode 100644 index 96b4539..0000000 --- a/Govor.Console/Commands/AdminListUserFriendshipsCommand.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Net.Http.Json; -using System.Text.Json; -using System.Threading.Tasks; -using Govor.Contracts.DTOs; // Required for FriendshipDto - -namespace Govor.ConsoleClient.Commands -{ - public class AdminListUserFriendshipsCommand : BaseCommand - { - public override async Task ExecuteAsync(string? argument) - { - if (!EnsureLoggedIn()) return; - - Guid userId; - if (string.IsNullOrWhiteSpace(argument) || !Guid.TryParse(argument, out userId)) - { - Console.Write("Введите ID пользователя для просмотра его связей: "); - var input = Console.ReadLine(); - if (string.IsNullOrWhiteSpace(input) || !Guid.TryParse(input, out userId)) - { - Console.WriteLine("[Ошибка] Неверный или пустой ID пользователя."); - return; - } - } - - Console.WriteLine($"Получение всех связей для пользователя {userId} (только для администраторов)..."); - try - { - var response = await HttpClientService.GetAsync($"api/admin/Friendships/{userId}"); - - var friendships = JsonSerializer.Deserialize>(response); - - if (friendships != null && friendships.Any()) - { - Console.WriteLine($"Дружеские связи для пользователя {userId}:"); - foreach (var f in friendships) - { - Console.WriteLine($"- ID: {f.Id}, Пользователь1: {f.RequesterId}, Пользователь2: {f.AddresseeId}, Статус: {f.Status}"); - } - } - else - { - Console.WriteLine($"Дружеские связи для пользователя {userId} не найдены."); - } - } - catch (HttpRequestException httpEx) when (httpEx.StatusCode == System.Net.HttpStatusCode.Forbidden) - { - Console.WriteLine("[Ошибка] Доступ запрещен. Эта команда только для администраторов."); - } - catch (Exception ex) - { - Console.WriteLine($"[Ошибка] {ex.Message}"); - } - } - - public override string GetHelp() - { - return "/adminlistuserfs [ID_пользователя] - (Админ) Показать все дружеские связи для указанного пользователя. Если ID не указан, запросит ввод."; - } - } -} diff --git a/Govor.Console/Commands/AdminRemoveFriendshipCommand.cs b/Govor.Console/Commands/AdminRemoveFriendshipCommand.cs deleted file mode 100644 index aa55ff9..0000000 --- a/Govor.Console/Commands/AdminRemoveFriendshipCommand.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Net.Http; -using System.Threading.Tasks; - -namespace Govor.ConsoleClient.Commands -{ - public class AdminRemoveFriendshipCommand : BaseCommand - { - public override async Task ExecuteAsync(string? argument) - { - if (!EnsureLoggedIn()) return; - - Guid friendshipId; - if (string.IsNullOrWhiteSpace(argument) || !Guid.TryParse(argument, out friendshipId)) - { - Console.Write("Введите ID дружеской связи для удаления: "); - var input = Console.ReadLine(); - if (string.IsNullOrWhiteSpace(input) || !Guid.TryParse(input, out friendshipId)) - { - Console.WriteLine("[Ошибка] Неверный или пустой ID связи."); - return; - } - } - - Console.WriteLine($"Удаление дружеской связи {friendshipId} (только для администраторов)..."); - try - { - // The API endpoint is: [HttpPost] public async Task RemoveFriendship(Guid Id) - // It expects the Id in the query string, like: api/admin/Friendships?Id=GUID - // Or it might be a POST with x-www-form-urlencoded data or JSON body, need to check API implementation. - // Based on `[HttpPost] public async Task RemoveFriendship(Guid Id)`, it's likely a query parameter or form data. - // Let's assume query parameter for now as it's simpler for HttpPost. - - var response = await HttpClientService.PostAsync($"api/admin/Friendships?Id={friendshipId}", null); - - Console.WriteLine($"Дружеская связь {friendshipId} успешно удалена."); - } - catch (HttpRequestException httpEx) when (httpEx.StatusCode == System.Net.HttpStatusCode.Forbidden) - { - Console.WriteLine("[Ошибка] Доступ запрещен. Эта команда только для администраторов."); - } - catch (HttpRequestException httpEx) when (httpEx.StatusCode == System.Net.HttpStatusCode.NotFound) - { - Console.WriteLine($"[Ошибка] Дружеская связь с ID {friendshipId} не найдена."); - } - catch (Exception ex) - { - Console.WriteLine($"[Ошибка] {ex.Message}"); - } - } - - public override string GetHelp() - { - return "/adminremovefs [ID_связи] - (Админ) Удалить дружескую связь по ее ID. Если ID не указан, запросит ввод."; - } - } -} diff --git a/Govor.Console/Commands/BaseCommand.cs b/Govor.Console/Commands/BaseCommand.cs deleted file mode 100644 index 9706fbe..0000000 --- a/Govor.Console/Commands/BaseCommand.cs +++ /dev/null @@ -1,65 +0,0 @@ -using Microsoft.AspNetCore.SignalR.Client; -using System; -using System.Threading.Tasks; - -namespace Govor.ConsoleClient.Commands -{ - public abstract class BaseCommand : ICommand - { - protected static FriendsClient? FriendsClient { get; private set; } - protected static HttpClientService HttpClientService { get; private set; } = null!; - protected static Func GetAuthToken { get; private set; } = null!; - protected static Action SetAuthToken { get; private set; } = null!; - protected static Func InitializeHubConnectionAsync { get; private set; } = null!; - protected static HubConnection HubConnection => _getHubConnection?.Invoke(); - - private static Func _getHubConnection = null!; - - public static void InitializeServices( - FriendsClient? friendsClient, - HttpClientService httpClientService, - Func getAuthToken, - Action setAuthToken, - Func initializeHubConnectionAsync, - Func getHubConnection) - { - FriendsClient = friendsClient; - HttpClientService = httpClientService; - GetAuthToken = getAuthToken; - SetAuthToken = setAuthToken; - InitializeHubConnectionAsync = initializeHubConnectionAsync; - _getHubConnection = getHubConnection; - } - - public abstract Task ExecuteAsync(string? argument); - public abstract string GetHelp(); - - protected bool EnsureLoggedIn() - { - if (GetAuthToken() == null) - { - Console.WriteLine("[Ошибка] Сначала войдите в систему. Используйте /login или /reg."); - return false; - } - - if (FriendsClient == null && !(this is LoginCommand || this is RegisterCommand)) - { - Console.WriteLine("[Ошибка] Клиент для работы с друзьями не инициализирован. Попробуйте войти снова."); - return false; - } - - return true; - } - - protected bool EnsureHubConnection() - { - var hub = _getHubConnection?.Invoke(); - if (hub == null || hub.State != HubConnectionState.Connected) - { - Console.WriteLine("[Ошибка] SignalR соединение не установлено. Попробуйте войти снова или проверьте соединение."); - return false; - } - return true; - } - } -} diff --git a/Govor.Console/Commands/BlockUserCommand.cs b/Govor.Console/Commands/BlockUserCommand.cs deleted file mode 100644 index bf83a39..0000000 --- a/Govor.Console/Commands/BlockUserCommand.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.SignalR.Client; - -namespace Govor.ConsoleClient.Commands -{ - public class BlockUserCommand : BaseCommand - { - public override async Task ExecuteAsync(string? argument) - { - if (!EnsureLoggedIn() || !EnsureHubConnection()) return; - - Guid targetUserId; - if (string.IsNullOrWhiteSpace(argument) || !Guid.TryParse(argument, out targetUserId)) - { - Console.Write("Введите ID пользователя, которого хотите заблокировать: "); - var input = Console.ReadLine(); - if (string.IsNullOrWhiteSpace(input) || !Guid.TryParse(input, out targetUserId)) - { - Console.WriteLine("[Ошибка] Неверный или пустой ID пользователя."); - return; - } - } - - try - { - // API uses Hub for this: BlockUser(Guid targetUserId) - // Located in Govor.API/Hubs/FriendsHub.cs - await HubConnection.InvokeAsync("BlockUser", targetUserId); - Console.WriteLine($"Пользователь {targetUserId} заблокирован."); - } - catch (Exception ex) - { - Console.WriteLine($"[Ошибка блокировки пользователя] {ex.Message}"); - } - } - - public override string GetHelp() - { - return "/block [ID_пользователя] - Заблокировать пользователя. Если ID не указан, запросит ввод."; - } - } -} diff --git a/Govor.Console/Commands/HelpCommand.cs b/Govor.Console/Commands/HelpCommand.cs deleted file mode 100644 index c331a83..0000000 --- a/Govor.Console/Commands/HelpCommand.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Govor.ConsoleClient.Commands -{ - public class HelpCommand : BaseCommand - { - private readonly Dictionary _availableCommands; - - // The HelpCommand needs access to the list of all commands to display their help messages. - // This will be injected from Program.cs where the commands are registered. - public HelpCommand(Dictionary availableCommands) - { - _availableCommands = availableCommands; - } - - public override Task ExecuteAsync(string? argument) - { - Console.WriteLine("Доступные команды:"); - if (!string.IsNullOrWhiteSpace(argument)) - { - string commandKey = argument.StartsWith("/") ? argument : "/" + argument; - if (_availableCommands.TryGetValue(commandKey.ToLower(), out var specificCommand)) - { - Console.WriteLine(specificCommand.GetHelp()); - } - else - { - Console.WriteLine($"Команда '{argument}' не найдена. Введите /help для списка всех команд."); - } - } - else - { - foreach (var commandEntry in _availableCommands.OrderBy(c => c.Key)) - { - Console.WriteLine(commandEntry.Value.GetHelp()); - } - } - return Task.CompletedTask; - } - - public override string GetHelp() - { - return "/help [команда] - Показать список всех команд или помощь по конкретной команде."; - } - } -} diff --git a/Govor.Console/Commands/ICommand.cs b/Govor.Console/Commands/ICommand.cs deleted file mode 100644 index 0e3c8ae..0000000 --- a/Govor.Console/Commands/ICommand.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Govor.ConsoleClient.Commands -{ - public interface ICommand - { - Task ExecuteAsync(string? argument); - string GetHelp(); - } -} diff --git a/Govor.Console/Commands/ListFriendsCommand.cs b/Govor.Console/Commands/ListFriendsCommand.cs deleted file mode 100644 index 1d4cf92..0000000 --- a/Govor.Console/Commands/ListFriendsCommand.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; - -namespace Govor.ConsoleClient.Commands -{ - public class ListFriendsCommand : BaseCommand - { - public override async Task ExecuteAsync(string? argument) - { - if (!EnsureLoggedIn()) return; - - try - { - var friends = await FriendsClient.GetFriendsAsync(); - if (friends.Any()) - { - Console.WriteLine("Ваши друзья:"); - foreach (var f in friends) - { - Console.WriteLine($"- {f.Username} | был онлайн: {f.WasOnline} [{f.Id}]"); - } - } - else - { - Console.WriteLine("У вас пока нет друзей."); - } - } - catch (Exception ex) - { - Console.WriteLine($"[Ошибка] {ex.Message}"); - } - } - - public override string GetHelp() - { - return "/friends - Показать список ваших друзей."; - } - } -} diff --git a/Govor.Console/Commands/ListIncomingRequestsCommand.cs b/Govor.Console/Commands/ListIncomingRequestsCommand.cs deleted file mode 100644 index 3bfa6a7..0000000 --- a/Govor.Console/Commands/ListIncomingRequestsCommand.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; - -namespace Govor.ConsoleClient.Commands -{ - public class ListIncomingRequestsCommand : BaseCommand - { - public override async Task ExecuteAsync(string? argument) - { - if (!EnsureLoggedIn()) return; - - try - { - // This still uses REST client as per existing FriendsClient - var requests = await FriendsClient.GetIncomingRequestsAsync(); - if (requests.Any()) - { - Console.WriteLine("Входящие заявки в друзья:"); - foreach (var r in requests) - { - // Assuming you want to show who sent the request. - // The FriendshipDto contains RequesterId and AddresseeId. - // If current user is AddresseeId, then RequesterId is the one who sent it. - Console.WriteLine($"- Запрос ID: {r.Id}. От пользователя ID: {r.RequesterId}. Статус: {r.Status}. (принять через /accept {r.Id})"); - } - } - else - { - Console.WriteLine("У вас нет входящих заявок в друзья."); - } - } - catch (Exception ex) - { - Console.WriteLine($"[Ошибка] {ex.Message}"); - } - } - - public override string GetHelp() - { - return "/incoming - Показать список входящих заявок в друзья."; - } - } -} diff --git a/Govor.Console/Commands/ListOutgoingRequestsCommand.cs b/Govor.Console/Commands/ListOutgoingRequestsCommand.cs deleted file mode 100644 index ffa272d..0000000 --- a/Govor.Console/Commands/ListOutgoingRequestsCommand.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Linq; -using System.Net.Http; -using System.Net.Http.Json; -using System.Text.Json; -using System.Threading.Tasks; -using Govor.Contracts.DTOs; // Required for FriendshipDto - -namespace Govor.ConsoleClient.Commands -{ - public class ListOutgoingRequestsCommand : BaseCommand - { - public override async Task ExecuteAsync(string? argument) - { - if (!EnsureLoggedIn()) return; - - try - { - // This functionality maps to GET api/friends/responses in FriendsRequestQueryController - // FriendsClient doesn't have a dedicated method, so we use HttpClientService or direct HttpClient - var json = await HttpClientService.GetAsync("api/friends/responses"); - - var requests = JsonSerializer.Deserialize>(json, new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true - }); - - if (requests != null && requests.Any()) - { - Console.WriteLine("Отправленные вами заявки в друзья (ожидают ответа):"); - foreach (var r in requests) - { - // If current user is RequesterId, then AddresseeId is the one they sent to. - Console.WriteLine($"- Запрос ID: {r.Id}. Пользователю ID: {r.AddresseeId}. Статус: {r.Status}."); - } - } - else - { - Console.WriteLine("У вас нет отправленных заявок, ожидающих ответа."); - } - } - catch (Exception ex) - { - Console.WriteLine($"[Ошибка] {ex.Message}"); - } - } - - public override string GetHelp() - { - return "/outgoing - Показать список отправленных вами заявок в друзья, ожидающих ответа."; - } - } -} diff --git a/Govor.Console/Commands/LoginCommand.cs b/Govor.Console/Commands/LoginCommand.cs deleted file mode 100644 index 9f2d017..0000000 --- a/Govor.Console/Commands/LoginCommand.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Net.Http.Headers; -using System.Threading.Tasks; - -namespace Govor.ConsoleClient.Commands -{ - public class LoginCommand : BaseCommand - { - public override async Task ExecuteAsync(string? argument) - { - Console.Write("username: "); - var loginUsername = Console.ReadLine(); - - Console.Write("password: "); - var loginPassword = Console.ReadLine(); - - if (string.IsNullOrWhiteSpace(loginUsername) || string.IsNullOrWhiteSpace(loginPassword)) - { - Console.WriteLine("[Ошибка] Имя пользователя и пароль не могут быть пустыми."); - return; - } - - try - { - var authToken = await HttpClientService.LoginAsync(loginUsername, loginPassword); - SetAuthToken(authToken); - - // Initialize FriendsClient after successful login - var sharedClient = new HttpClient { BaseAddress = new Uri(HttpClientService.GetBaseUrl()) }; - sharedClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken); - var friendsClient = new FriendsClient(sharedClient); - - - - Program.UpdateFriendsClient(friendsClient); // <-- единственный нужный вызов - - await InitializeHubConnectionAsync(); - Console.WriteLine("[Успех] Вход выполнен. Токен сохранен."); - } - catch (Exception ex) - { - Console.WriteLine($"[Ошибка] {ex.Message}"); - } - } - - public override string GetHelp() - { - return "/login - Войти в существующий аккаунт."; - } - } -} diff --git a/Govor.Console/Commands/RegisterCommand.cs b/Govor.Console/Commands/RegisterCommand.cs deleted file mode 100644 index 2dd84e7..0000000 --- a/Govor.Console/Commands/RegisterCommand.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Net.Http.Headers; -using System.Threading.Tasks; - -namespace Govor.ConsoleClient.Commands -{ - public class RegisterCommand : BaseCommand - { - public override async Task ExecuteAsync(string? argument) - { - Console.Write("username: "); - var regUsername = Console.ReadLine(); - - Console.Write("password: "); - var regPassword = Console.ReadLine(); - - Console.Write("invitation code: "); - var inviteCode = Console.ReadLine(); - - if (string.IsNullOrWhiteSpace(regUsername) || string.IsNullOrWhiteSpace(regPassword) || string.IsNullOrWhiteSpace(inviteCode)) - { - Console.WriteLine("[Ошибка] Имя пользователя, пароль и код приглашения не могут быть пустыми."); - return; - } - - try - { - var authToken = await HttpClientService.RegisterAsync(regUsername, regPassword, inviteCode); - SetAuthToken(authToken); - - // Initialize FriendsClient after successful registration - var sharedClient = new HttpClient { BaseAddress = new Uri(HttpClientService.GetBaseUrl()) }; - sharedClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken); - var friendsClient = new FriendsClient(sharedClient); - - // Re-initialize services in BaseCommand with the new FriendsClient - Program.UpdateFriendsClient(friendsClient); // <-- единственный нужный вызов - - await InitializeHubConnectionAsync(); - Console.WriteLine("[Успех] Регистрация завершена. Токен сохранен."); - } - catch (Exception ex) - { - Console.WriteLine($"[Ошибка] {ex.Message}"); - } - } - - public override string GetHelp() - { - return "/reg - Зарегистрировать новый аккаунт с помощью кода приглашения."; - } - } -} diff --git a/Govor.Console/Commands/RejectFriendRequestCommand.cs b/Govor.Console/Commands/RejectFriendRequestCommand.cs deleted file mode 100644 index afd9673..0000000 --- a/Govor.Console/Commands/RejectFriendRequestCommand.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.SignalR.Client; - -namespace Govor.ConsoleClient.Commands -{ - public class RejectFriendRequestCommand : BaseCommand - { - public override async Task ExecuteAsync(string? argument) - { - if (!EnsureLoggedIn() || !EnsureHubConnection()) return; - - Guid friendshipId; - if (string.IsNullOrWhiteSpace(argument) || !Guid.TryParse(argument, out friendshipId)) - { - Console.Write("Введите ID заявки, которую хотите отклонить: "); - var input = Console.ReadLine(); - if (string.IsNullOrWhiteSpace(input) || !Guid.TryParse(input, out friendshipId)) - { - Console.WriteLine("[Ошибка] Неверный или пустой ID заявки."); - return; - } - } - - try - { - // API uses Hub for this: RejectFriendRequest(Guid friendshipId) - // Located in Govor.API/Hubs/FriendsHub.cs - await HubConnection.InvokeAsync("RejectRequest", friendshipId); - Console.WriteLine("Заявка в друзья отклонена."); - } - catch (Exception ex) - { - Console.WriteLine($"[Ошибка отклонения заявки] {ex.Message}"); - } - } - - public override string GetHelp() - { - return "/reject [ID_заявки] - Отклонить входящую заявку в друзья. Если ID не указан, запросит ввод."; - } - } -} diff --git a/Govor.Console/Commands/SearchUserCommand.cs b/Govor.Console/Commands/SearchUserCommand.cs deleted file mode 100644 index 1c00d31..0000000 --- a/Govor.Console/Commands/SearchUserCommand.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; - -namespace Govor.ConsoleClient.Commands -{ - public class SearchUserCommand : BaseCommand - { - public override async Task ExecuteAsync(string? argument) - { - if (!EnsureLoggedIn()) return; - - if (string.IsNullOrWhiteSpace(argument)) - { - Console.Write("Введите имя пользователя для поиска: "); - argument = Console.ReadLine(); - if (string.IsNullOrWhiteSpace(argument)) - { - Console.WriteLine("[Ошибка] Поисковый запрос не может быть пустым."); - return; - } - } - - try - { - var foundUsers = await FriendsClient.SearchAsync(argument); - if (foundUsers.Any()) - { - Console.WriteLine("Найденные пользователи:"); - foreach (var user in foundUsers) - { - Console.WriteLine($"- {user.Username} [{user.Id}]"); - } - } - else - { - Console.WriteLine("Пользователи не найдены."); - } - } - catch (Exception ex) - { - Console.WriteLine($"[Ошибка] {ex.Message}"); - } - } - - public override string GetHelp() - { - return "/search [имя_пользователя] - Найти пользователей по имени. Если имя не указано, запросит ввод."; - } - } -} diff --git a/Govor.Console/Commands/SendFriendRequestCommand.cs b/Govor.Console/Commands/SendFriendRequestCommand.cs deleted file mode 100644 index 62c65fe..0000000 --- a/Govor.Console/Commands/SendFriendRequestCommand.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.SignalR.Client; - -namespace Govor.ConsoleClient.Commands -{ - public class SendFriendRequestCommand : BaseCommand - { - public override async Task ExecuteAsync(string? argument) - { - if (!EnsureLoggedIn() || !EnsureHubConnection()) return; - - Guid targetUserId; - if (string.IsNullOrWhiteSpace(argument) || !Guid.TryParse(argument, out targetUserId)) - { - Console.Write("Введите ID пользователя, которому хотите отправить запрос: "); - var input = Console.ReadLine(); - if (string.IsNullOrWhiteSpace(input) || !Guid.TryParse(input, out targetUserId)) - { - Console.WriteLine("[Ошибка] Неверный или пустой ID пользователя."); - return; - } - } - - try - { - // API uses Hub for this: SendFriendRequest(Guid targetUserId) - await HubConnection.InvokeAsync("SendRequest", targetUserId); - Console.WriteLine("Запрос в друзья отправлен."); - } - catch (Exception ex) - { - Console.WriteLine($"[Ошибка отправки запроса] {ex.Message}"); - } - } - - public override string GetHelp() - { - return "/friend [ID_пользователя] - Отправить запрос в друзья пользователю с указанным ID. Если ID не указан, запросит ввод."; - } - } -} diff --git a/Govor.Console/Commands/UnblockUserCommand.cs b/Govor.Console/Commands/UnblockUserCommand.cs deleted file mode 100644 index 5fa17fc..0000000 --- a/Govor.Console/Commands/UnblockUserCommand.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.SignalR.Client; - -namespace Govor.ConsoleClient.Commands -{ - public class UnblockUserCommand : BaseCommand - { - public override async Task ExecuteAsync(string? argument) - { - if (!EnsureLoggedIn() || !EnsureHubConnection()) return; - - Guid targetUserId; - if (string.IsNullOrWhiteSpace(argument) || !Guid.TryParse(argument, out targetUserId)) - { - Console.Write("Введите ID пользователя, которого хотите разблокировать: "); - var input = Console.ReadLine(); - if (string.IsNullOrWhiteSpace(input) || !Guid.TryParse(input, out targetUserId)) - { - Console.WriteLine("[Ошибка] Неверный или пустой ID пользователя."); - return; - } - } - - try - { - // API uses Hub for this: UnblockUser(Guid targetUserId) - // Located in Govor.API/Hubs/FriendsHub.cs - await HubConnection.InvokeAsync("UnblockUser", targetUserId); - Console.WriteLine($"Пользователь {targetUserId} разблокирован."); - } - catch (Exception ex) - { - Console.WriteLine($"[Ошибка разблокировки пользователя] {ex.Message}"); - } - } - - public override string GetHelp() - { - return "/unblock [ID_пользователя] - Разблокировать пользователя. Если ID не указан, запросит ввод."; - } - } -} diff --git a/Govor.Console/FriendsHubClient.cs b/Govor.Console/FriendsHubClient.cs index f8667d2..fdc342b 100644 --- a/Govor.Console/FriendsHubClient.cs +++ b/Govor.Console/FriendsHubClient.cs @@ -1,157 +1,70 @@ +using Microsoft.AspNetCore.SignalR.Client; using System; using System.Threading.Tasks; -using Microsoft.AspNetCore.SignalR; -using Microsoft.AspNetCore.SignalR.Client; +using Govor.Contracts.Responses.SignalR; -class FriendsHubClient +namespace Govor.ConsoleClient; + + +public class FriendsHubClient { - private readonly HubConnection _connection; + private readonly string _hubUrl; + private readonly string _jwtToken; + private HubConnection _connection; public FriendsHubClient(string hubUrl, string jwtToken) + { + _hubUrl = hubUrl; + _jwtToken = jwtToken; + } + + public async Task ConnectAsync() { _connection = new HubConnectionBuilder() - .WithUrl(hubUrl, options => + .WithUrl($"{_hubUrl}/friends", options => { - options.AccessTokenProvider = () => Task.FromResult(jwtToken); + options.AccessTokenProvider = () => Task.FromResult(_jwtToken); }) .WithAutomaticReconnect() .Build(); - // Подписка на события от сервера - _connection.On("FriendRequestReceived", OnFriendRequestReceived); - _connection.On("FriendRequestAccepted", OnFriendRequestAccepted); - _connection.On("FriendRequestRejected", OnFriendRequestRejected); - } + // Событие: получен входящий запрос в друзья + _connection.On("FriendRequestReceived", userId => + { + Console.WriteLine($"📨 Friend request received from: {userId}"); + }); - private void OnFriendRequestReceived(Guid userId) - { - Console.WriteLine($"[Event] Получен запрос в друзья от пользователя: {userId}"); - } + // Событие: запрос принят + _connection.On("FriendRequestAccepted", friendshipId => + { + Console.WriteLine($"✅ Friend request accepted! Friendship ID: {friendshipId}"); + }); - private void OnFriendRequestAccepted(Guid friendshipId) - { - Console.WriteLine($"[Event] Запрос в друзья принят. ID дружбы: {friendshipId}"); - } + // Событие: запрос отклонён + _connection.On("FriendRequestRejected", friendshipId => + { + Console.WriteLine($"❌ Friend request rejected! Friendship ID: {friendshipId}"); + }); - private void OnFriendRequestRejected(Guid friendshipId) - { - Console.WriteLine($"[Event] Запрос в друзья отклонён. ID дружбы: {friendshipId}"); - } - - public async Task StartAsync() - { await _connection.StartAsync(); - Console.WriteLine("Подключено к FriendsHub"); + Console.WriteLine("✅ Connected to FriendsHub"); } - public async Task StopAsync() + public async Task SendFriendRequestAsync(Guid targetUserId) { - await _connection.StopAsync(); - Console.WriteLine("Отключено от FriendsHub"); + var result = await _connection.InvokeAsync>("SendRequest", targetUserId); + Console.WriteLine($"📤 Friend request sent. Status: {result.Status}"); } - public async Task SendRequest(Guid targetUserId) + public async Task AcceptFriendRequestAsync(Guid friendshipId) { - try - { - await _connection.InvokeAsync("SendRequest", targetUserId); - Console.WriteLine($"Запрос на добавление в друзья отправлен пользователю {targetUserId}"); - } - catch (HubException ex) - { - Console.WriteLine($"Ошибка при отправке запроса: {ex.Message}"); - } + var result = await _connection.InvokeAsync>("AcceptRequest", friendshipId); + Console.WriteLine($"👍 Accept result: {result.Status}"); } - public async Task AcceptRequest(Guid friendshipId) + public async Task RejectFriendRequestAsync(Guid friendshipId) { - try - { - await _connection.InvokeAsync("AcceptRequest", friendshipId); - Console.WriteLine($"Запрос в друзья с ID {friendshipId} принят"); - } - catch (HubException ex) - { - Console.WriteLine($"Ошибка при принятии запроса: {ex.Message}"); - } - } - - public async Task RejectRequest(Guid friendshipId) - { - try - { - await _connection.InvokeAsync("RejectRequest", friendshipId); - Console.WriteLine($"Запрос в друзья с ID {friendshipId} отклонён"); - } - catch (HubException ex) - { - Console.WriteLine($"Ошибка при отклонении запроса: {ex.Message}"); - } - } -} - -class Program -{ - static async Task TestMain(string[] args) - { - // Адрес хаба SignalR - string hubUrl = "https://yourserver.com/api/friends"; - - // JWT токен для аутентификации (замени на настоящий) - string jwtToken = "your_jwt_token_here"; - - var client = new FriendsHubClient(hubUrl, jwtToken); - - await client.StartAsync(); - - Console.WriteLine("Команды:"); - Console.WriteLine("send - Отправить запрос в друзья"); - Console.WriteLine("accept - Принять запрос"); - Console.WriteLine("reject - Отклонить запрос"); - Console.WriteLine("exit - Выйти"); - - while (true) - { - Console.Write("> "); - var input = Console.ReadLine(); - if (string.IsNullOrWhiteSpace(input)) - continue; - - var parts = input.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries); - var command = parts[0].ToLowerInvariant(); - - if (command == "exit") - break; - - if (parts.Length < 2) - { - Console.WriteLine("Ошибка: не хватает аргументов"); - continue; - } - - if (!Guid.TryParse(parts[1], out var id)) - { - Console.WriteLine("Ошибка: некорректный GUID"); - continue; - } - - switch (command) - { - case "send": - await client.SendRequest(id); - break; - case "accept": - await client.AcceptRequest(id); - break; - case "reject": - await client.RejectRequest(id); - break; - default: - Console.WriteLine("Неизвестная команда"); - break; - } - } - - await client.StopAsync(); + var result = await _connection.InvokeAsync>("RejectRequest", friendshipId); + Console.WriteLine($"👎 Reject result: {result.Status}"); } } diff --git a/Govor.Console/Govor.Console.csproj b/Govor.Console/Govor.Console.csproj index 6f99954..0f9919f 100644 --- a/Govor.Console/Govor.Console.csproj +++ b/Govor.Console/Govor.Console.csproj @@ -10,6 +10,7 @@ + diff --git a/Govor.Console/Program.cs b/Govor.Console/Program.cs index 2247a05..9ac0bbf 100644 --- a/Govor.Console/Program.cs +++ b/Govor.Console/Program.cs @@ -1,382 +1,53 @@ -using Microsoft.AspNetCore.SignalR.Client; -using System; -using System.Collections.Generic; -using System.IdentityModel.Tokens.Jwt; -using System.Linq; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Threading.Tasks; -using Govor.ConsoleClient.Commands; -using Govor.Contracts.Requests.SignalR; -using Govor.Contracts.Responses.SignalR; -using Govor.Core.Models; -using Govor.Core.Models.Messages; +using Govor.ConsoleClient; -/*==================================== - *Личные сообщения| Егор - *==================================== - * [15:59] Вы добавили (Егор) в друзья - * - * [16:30] Егор >> Привет! - * [16:31] Ты >> Че как? - * - *------------------------------------ - * >> "Пойдем завтра гулять?!" - */ - -namespace Govor.ConsoleClient +internal class Program { - class Program + private const string HubBaseUrl = "https://govor-team-govor-88b3.twc1.net/hubs"; + private static string jwtToken = "eyJhbGciOiJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNobWFjLXNoYTI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI4NDRjNzkyMi1hNjdlLTRlMzctYWI0MC0wNThlZDE5NzM5NjMiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJBZG1pbiIsImV4cCI6MTc1MjQ4MzE0MH0.ECXLPwIljdeWgWUJtBiXXQXKMFC8Iw0x7_zdjbsUe1I"; // сюда подставьте валидный JWT + + static async Task Main() { - static string baseUrl = "https://govor-team-govor-88b3.twc1.net"; - //static string baseUrl = "https://localhost:7155"; - static string? AuthToken = null; - static HttpClientService HttpService = new(baseUrl); - private static FriendsClient? _friendsClient; // Renamed to avoid conflict with BaseCommand - static HubConnection? _hubConnection; - static Dictionary> ChatHistory = new(); - static string? CurrentChatUser = null; - static Guid CurrentChatUserId = Guid.Empty; + Console.Write("🔑 JWT Token: "); + jwtToken = Console.ReadLine(); - private static Dictionary _commands = new(); + var client = new FriendsHubClient(HubBaseUrl, jwtToken); + await client.ConnectAsync(); - static async Task Main() + while (true) { - FriendsHubClient client = new FriendsHubClient($"{baseUrl}/hubs/friends", "eyJhbGciOiJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNobWFjLXNoYTI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI4NDRjNzkyMi1hNjdlLTRlMzctYWI0MC0wNThlZDE5NzM5NjMiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJBZG1pbiIsImV4cCI6MTc1MjMzMjE2NX0.mGSjSCvguEXkdHqHcbZ3eUH0S8c82kz3XP89potEh1k"); - - await client.StartAsync(); - await client.AcceptRequest(Guid.Parse("3ba77c0c-7522-47be-8e77-ea47bd6c6e69")); - - return; - InitializeCommands(); - BaseCommand.InitializeServices( - _friendsClient, // Initially null, will be set by Login/Register commands - HttpService, - () => AuthToken, - (token) => AuthToken = token, - InitializeHubConnection, - () => _hubConnection - ); + Console.WriteLine("\n1. Send friend request"); + Console.WriteLine("2. Accept request"); + Console.WriteLine("3. Reject request"); + Console.Write("➡ Choose option: "); + var option = Console.ReadLine(); - LoginWindow(); - - while (true) + switch (option) { - Console.Write(CurrentChatUser == null ? "/> " : ">> "); - var input = Console.ReadLine(); - - if (string.IsNullOrWhiteSpace(input)) continue; - - if (input.StartsWith("/")) - await HandleCommandAsync(input); - else if (CurrentChatUser != null) - await SendMessageAsync(input); - else - Console.WriteLine("[Система] Введите команду, например /help или /friends"); + case "1": + Console.Write("Target UserId: "); + if (Guid.TryParse(Console.ReadLine(), out var targetId)) + { + await client.SendFriendRequestAsync(targetId); + } + break; + case "2": + Console.Write("FriendshipId to accept: "); + if (Guid.TryParse(Console.ReadLine(), out var acceptId)) + { + await client.AcceptFriendRequestAsync(acceptId); + } + break; + case "3": + Console.Write("FriendshipId to reject: "); + if (Guid.TryParse(Console.ReadLine(), out var rejectId)) + { + await client.RejectFriendRequestAsync(rejectId); + } + break; + default: + Console.WriteLine("❗ Unknown option"); + break; } } - - static void InitializeCommands() - { - _commands = new Dictionary - { - { "/login", new LoginCommand() }, - { "/reg", new RegisterCommand() }, - { "/search", new SearchUserCommand() }, - { "/friends", new ListFriendsCommand() }, - { "/friend", new SendFriendRequestCommand() }, // Alias for SendFriendRequest - { "/sendrequest", new SendFriendRequestCommand() }, - { "/accept", new AcceptFriendRequestCommand() }, - { "/reject", new RejectFriendRequestCommand() }, - { "/incoming", new ListIncomingRequestsCommand() }, - { "/outgoing", new ListOutgoingRequestsCommand() }, - { "/block", new BlockUserCommand() }, - { "/unblock", new UnblockUserCommand() }, - - { "/adminlistallfs", new AdminListAllFriendshipsCommand() }, - { "/adminlistuserfs", new AdminListUserFriendshipsCommand() }, - { "/adminremovefs", new AdminRemoveFriendshipCommand() }, - // Help command is special as it needs the list of commands - // It will be added after other commands are initialized - }; - _commands.Add("/help", new HelpCommand(_commands)); - _commands.Add("-h", new HelpCommand(_commands)); // Alias for help - - // Add other general commands not refactored yet if any, or create classes for them - // For example, /exit, /chat, /back will be handled separately for now or made into commands - } - - // Public static method to allow commands (like Login/Register) to update the FriendsClient - public static void UpdateFriendsClient(FriendsClient newFriendsClient) - { - _friendsClient = newFriendsClient; - // Re-initialize services in BaseCommand with the new FriendsClient - BaseCommand.InitializeServices( - _friendsClient, - HttpService, - () => AuthToken, - (token) => AuthToken = token, - InitializeHubConnection, - () => _hubConnection - ); - } - - - static async Task HandleCommandAsync(string input) - { - var parts = input.Split(new[] { ' ' }, 2, StringSplitOptions.RemoveEmptyEntries); - var commandKey = parts[0].ToLower(); - var argument = parts.Length > 1 ? parts[1] : null; - - if (_commands.TryGetValue(commandKey, out var commandInstance)) - { - try - { - await commandInstance.ExecuteAsync(argument); - } - catch (Exception ex) - { - Console.WriteLine($"[Критическая ошибка выполнения команды {commandKey}]: {ex.Message}"); - // Log detailed exception: Console.WriteLine(ex.ToString()); - } - } - else - { - // Handle non-refactored commands for now - switch (commandKey) - { - case "/exit": - Console.WriteLine("[Система] Выход..."); - if (_hubConnection != null) - { - await _hubConnection.StopAsync(); - await _hubConnection.DisposeAsync(); - } - Environment.Exit(0); - break; - case "/chat": - if (string.IsNullOrEmpty(argument)) - { - Console.WriteLine("[Ошибка] Укажите имя друга и ID через пробел (например /chat Egor GUID)"); - break; - } - - var chatArgs = argument.Split(' ', 2); - if (chatArgs.Length < 2 || !Guid.TryParse(chatArgs[1], out var chatUserId)) - { - Console.WriteLine("[Ошибка] Неверный формат. Укажите имя друга и ID (например /chat Egor GUID)"); - break; - } - - CurrentChatUser = chatArgs[0]; - CurrentChatUserId = chatUserId; - ShowChat(CurrentChatUser); - break; - - case "/back": - CurrentChatUser = null; - CurrentChatUserId = Guid.Empty; - Console.Clear(); // Optionally clear console when going back - LoginWindow(); // Or some other general view - break; - default: - Console.WriteLine($"[Ошибка] Неизвестная команда: {commandKey}. Введите /help для списка команд."); - break; - } - } - } - - static async Task InitializeHubConnection() - { - if (string.IsNullOrEmpty(AuthToken)) - { - // This check might be redundant if commands calling this ensure login first - Console.WriteLine("[Ошибка] Токен аутентификации отсутствует. Пожалуйста, войдите в систему."); - return; - } - - if (_hubConnection != null && _hubConnection.State != HubConnectionState.Disconnected) - { - Console.WriteLine("[SignalR] Соединение уже установлено или устанавливается."); - return; - } - - _hubConnection = new HubConnectionBuilder() - .WithUrl($"{baseUrl}/hubs/chats", options => - { - options.AccessTokenProvider = () => Task.FromResult(AuthToken); - }) - .Build(); - - _hubConnection.On("ReceiveMessage", (message) => - { - var myId = GetMyUserId(); - var senderName = message.SenderId == myId ? "Ты" : CurrentChatUser ?? message.SenderId.ToString() ?? "Неизвестно"; - var displayMessage = $"[{message.SentAt:HH:mm}] {senderName} >> {message.EncryptedContent}"; - - // Determine chat key based on sender/recipient, ensuring consistency - string chatKey = message.SenderId == myId ? message.RecipientId.ToString() : message.SenderId.ToString(); - string activeChatDisplayUser = CurrentChatUser; // User whose chat window is open - - // If the message belongs to the currently active chat window - if (CurrentChatUser != null && (message.SenderId == CurrentChatUserId || (message.RecipientId == CurrentChatUserId && message.SenderId == myId))) - { - if (!ChatHistory.ContainsKey(activeChatDisplayUser)) - { - ChatHistory[activeChatDisplayUser] = new List(); - } - ChatHistory[activeChatDisplayUser].Add(displayMessage); - - // Smartly update console without disrupting user input too much - int originalCursorTop = Console.CursorTop; - int originalCursorLeft = Console.CursorLeft; - Console.MoveBufferArea(0, originalCursorTop, Console.BufferWidth, 1, 0, originalCursorTop +1); // scroll current line down - Console.SetCursorPosition(0, originalCursorTop); - Console.WriteLine($" * {displayMessage}"); - Console.SetCursorPosition(originalCursorLeft, originalCursorTop +1); // +1 because writeline added a line - Console.Write(">> "); // Re-display prompt part - } - else // Notification for message not in the current chat - { - Console.WriteLine($"\n[Новое сообщение от {message.SenderId.ToString()}]: {message.EncryptedContent}"); - Console.Write(CurrentChatUser == null ? "/> " : ">> "); // Re-display prompt - } - }); - - _hubConnection.On("MessageSent", (message) => - { - // Optional: Handle confirmation that message was sent successfully by the server - // For console, maybe a subtle notification or log if verbose mode is on. - // Example: Console.WriteLine($"[Система] Сообщение для {message.RecipientUsername} доставлено на сервер."); - }); - - _hubConnection.On("FriendRequestReceived", (message) => // Assuming message is a simple string notification - { - Console.WriteLine($"\n[Система] Новый запрос в друзья: {message}"); - Console.Write(CurrentChatUser == null ? "/> " : ">> "); - }); - - _hubConnection.On("FriendRequestAccepted", (message) => - { - Console.WriteLine($"\n[Система] Ваш запрос в друзья принят: {message}"); - Console.Write(CurrentChatUser == null ? "/> " : ">> "); - }); - - _hubConnection.On("FriendRemoved", (message) => - { - Console.WriteLine($"\n[Система] Пользователь удалил вас из друзей или вы удалили его: {message}"); - Console.Write(CurrentChatUser == null ? "/> " : ">> "); - }); - - _hubConnection.On("UserBlocked", (message) => - { - Console.WriteLine($"\n[Система] Пользователь заблокирован: {message}"); - Console.Write(CurrentChatUser == null ? "/> " : ">> "); - }); - _hubConnection.On("UserUnblocked", (message) => - { - Console.WriteLine($"\n[Система] Пользователь разблокирован: {message}"); - Console.Write(CurrentChatUser == null ? "/> " : ">> "); - }); - - - try - { - await _hubConnection.StartAsync(); - Console.WriteLine("[SignalR] Соединение установлено."); - } - catch (Exception ex) - { - Console.WriteLine($"[SignalR Ошибка] {ex.Message}"); - } - } - - static Guid GetMyUserId() - { - if (AuthToken == null) return Guid.Empty; - var handler = new JwtSecurityTokenHandler(); - if (!handler.CanReadToken(AuthToken)) return Guid.Empty; - var jwtToken = handler.ReadJwtToken(AuthToken); - var userIdClaim = jwtToken.Claims.FirstOrDefault(claim => claim.Type == "userId" || claim.Type == System.Security.Claims.ClaimTypes.NameIdentifier); - return userIdClaim != null && Guid.TryParse(userIdClaim.Value, out var userId) ? userId : Guid.Empty; - } - - - static void ShowChat(string user) - { - Console.Clear(); - Console.WriteLine($"/*===================================="); - Console.WriteLine($" * Личные сообщения | {user} ({CurrentChatUserId})"); - Console.WriteLine($" * (/back для выхода из чата)"); - Console.WriteLine($" *===================================="); - - if (!ChatHistory.ContainsKey(user)) - { - ChatHistory[user] = new List(); - // Potentially load chat history here via an API call if desired - // ChatHistory[user].Add($"[Система] Начало чата с {user}. Введите сообщение или /back."); - } - else - { - foreach (var msg in ChatHistory[user]) - Console.WriteLine(" * " + msg); - } - Console.WriteLine(" *------------------------------------"); - } - - static async Task SendMessageAsync(string messageContent) - { - if (string.IsNullOrWhiteSpace(messageContent) || _hubConnection == null || CurrentChatUserId == Guid.Empty) return; - - var myUserId = GetMyUserId(); - if (myUserId == Guid.Empty) - { - Console.WriteLine("[Ошибка] Не удалось определить ID пользователя. Попробуйте войти снова."); - return; - } - - var messageRequest = new MessageRequest - { - RecipientId = CurrentChatUserId, - EncryptedContent = messageContent, - RecipientType = RecipientType.User - }; - - try - { - await _hubConnection.InvokeAsync("Send", messageRequest); - - var displayMessage = $"[{DateTime.Now:HH:mm}] Ты >> {messageContent}"; - if (!ChatHistory.ContainsKey(CurrentChatUser!)) - { - ChatHistory[CurrentChatUser!] = new List(); - } - ChatHistory[CurrentChatUser!].Add(displayMessage); - // No Console.WriteLine here, message will be echoed by "ReceiveMessage" if server sends it to self, - // or we rely on user seeing their own typed message. - // The ReceiveMessage handler should ideally handle self-messages for consistent display. - } - catch (Exception ex) - { - Console.WriteLine($"[Ошибка отправки] {ex.Message}"); - } - } - - static void LoginWindow() - { - Console.WriteLine($"/*===================================="); - Console.WriteLine($" * Добро пожаловать в Govor!"); - Console.WriteLine($" *===================================="); - Console.WriteLine(" * /reg - создать аккаунт "); - Console.WriteLine(" * /login - войти"); - Console.WriteLine(" * /help - список команд"); - } } -} - -// LoginResponse class was here, can be removed if not used elsewhere or moved to a Contracts project. -// Assuming it's implicitly handled by HttpClientService or was specific to the old Program.cs structure. -// public class LoginResponse -// { -// public string token { get; set; } -// } \ No newline at end of file +} \ No newline at end of file diff --git a/Govor.Contracts/Govor.Contracts.csproj b/Govor.Contracts/Govor.Contracts.csproj index 20e0a8f..1618bf5 100644 --- a/Govor.Contracts/Govor.Contracts.csproj +++ b/Govor.Contracts/Govor.Contracts.csproj @@ -10,4 +10,10 @@ + + + ..\..\..\..\..\Program Files\dotnet\shared\Microsoft.AspNetCore.App\8.0.17\Microsoft.AspNetCore.Http.Features.dll + + + diff --git a/Govor.Contracts/Requests/MediaUploadRequest.cs b/Govor.Contracts/Requests/MediaUploadRequest.cs index 4408dc1..32a8a99 100644 --- a/Govor.Contracts/Requests/MediaUploadRequest.cs +++ b/Govor.Contracts/Requests/MediaUploadRequest.cs @@ -1,10 +1,13 @@ +using System.ComponentModel.DataAnnotations; using Govor.Core.Models.Messages; +using Microsoft.AspNetCore.Http; namespace Govor.Contracts.Requests; public class MediaUploadRequest { - public byte[] Data { get; set; } + [Required] + public IFormFile Data { get; set; } public string FileName { get; set; } public string EncryptedKey { get; set; } = string.Empty; public MediaType Type { get; set; } diff --git a/Govor.Contracts/Responses/SignalR/MessageEditResponse.cs b/Govor.Contracts/Responses/SignalR/MessageEditResponse.cs index a82ecca..f03c638 100644 --- a/Govor.Contracts/Responses/SignalR/MessageEditResponse.cs +++ b/Govor.Contracts/Responses/SignalR/MessageEditResponse.cs @@ -1,6 +1,13 @@ +using Govor.Core.Models.Messages; + namespace Govor.Contracts.Responses.SignalR; public class MessageEditResponse { - + public Guid MessageId { get; set; } + public Guid EditorId { get; set; } + public Guid RecipientId { get; init; } + public RecipientType RecipientType{get; init; } + public string NewEncryptedContent { get; set; } = string.Empty; + public DateTime EditedAt { get; set; } } \ No newline at end of file diff --git a/Govor.Core/Models/MediaFile.cs b/Govor.Core/Models/MediaFile.cs index 6023416..50d8e59 100644 --- a/Govor.Core/Models/MediaFile.cs +++ b/Govor.Core/Models/MediaFile.cs @@ -5,6 +5,7 @@ namespace Govor.Core.Models; public class MediaFile { public Guid Id { get; set; } + public Guid UploaderId { get; set; } public string Url { get; set; } public MediaType MediaType { get; set; } public string MineType { get; set; } diff --git a/Govor.Core/Models/Users/PrivacyUserSettings.cs b/Govor.Core/Models/Users/PrivacyUserSettings.cs index 1fece99..c0a19a8 100644 --- a/Govor.Core/Models/Users/PrivacyUserSettings.cs +++ b/Govor.Core/Models/Users/PrivacyUserSettings.cs @@ -25,7 +25,7 @@ public enum WhoCan { None = 0, OnlyFriends = 1, - EveryoneCanSend = 2, + Everyone = 2, } public enum DeletingMessagesVia diff --git a/Govor.Data/Migrations/20250713101943_AddUploaderIdToMediaFile.Designer.cs b/Govor.Data/Migrations/20250713101943_AddUploaderIdToMediaFile.Designer.cs new file mode 100644 index 0000000..69fad3a --- /dev/null +++ b/Govor.Data/Migrations/20250713101943_AddUploaderIdToMediaFile.Designer.cs @@ -0,0 +1,596 @@ +// +using System; +using Govor.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Govor.Data.Migrations +{ + [DbContext(typeof(GovorDbContext))] + [Migration("20250713101943_AddUploaderIdToMediaFile")] + partial class AddUploaderIdToMediaFile + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("Govor.Core.Models.ChatGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("ImageId") + .HasColumnType("char(36)"); + + b.Property("IsChannel") + .HasColumnType("tinyint(1)"); + + b.Property("IsPrivate") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("Id"); + + b.ToTable("ChatGroups"); + }); + + modelBuilder.Entity("Govor.Core.Models.Friendship", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AddresseeId") + .HasColumnType("char(36)"); + + b.Property("RequesterId") + .HasColumnType("char(36)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("AddresseeId"); + + b.HasIndex("RequesterId"); + + b.ToTable("Friendships"); + }); + + modelBuilder.Entity("Govor.Core.Models.GroupAdmins", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupAdmins"); + }); + + modelBuilder.Entity("Govor.Core.Models.GroupInvitation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("EndDate") + .HasColumnType("datetime(6)"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("InvitationCode") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("MaxParticipants") + .HasColumnType("int"); + + b.Property("UserMakerId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("GroupId"); + + b.HasIndex("UserMakerId"); + + b.ToTable("GroupInvitations"); + }); + + modelBuilder.Entity("Govor.Core.Models.GroupMembership", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("InvitationId") + .IsRequired() + .HasColumnType("char(36)"); + + b.Property("IsBanned") + .HasColumnType("tinyint(1)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("GroupId"); + + b.HasIndex("InvitationId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("Govor.Core.Models.Invitation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Code") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("DateCreated") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("EndDate") + .HasColumnType("datetime(6)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("IsAdmin") + .HasColumnType("tinyint(1)"); + + b.Property("MaxParticipants") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Invitations"); + }); + + modelBuilder.Entity("Govor.Core.Models.MediaFile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("DateCreated") + .HasColumnType("datetime(6)"); + + b.Property("MediaType") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("MineType") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("varchar(128)"); + + b.Property("UploaderId") + .HasColumnType("char(36)"); + + b.Property("Url") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("MediaFiles"); + }); + + modelBuilder.Entity("Govor.Core.Models.Messages.MediaAttachments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("MediaFileId") + .HasColumnType("char(36)"); + + b.Property("MessageId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("MediaFileId"); + + b.HasIndex("MessageId"); + + b.ToTable("MediaAttachments"); + }); + + modelBuilder.Entity("Govor.Core.Models.Messages.Message", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ChatGroupId") + .HasColumnType("char(36)"); + + b.Property("EditedAt") + .HasColumnType("datetime(6)"); + + b.Property("EncryptedContent") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IsEdited") + .HasColumnType("tinyint(1)"); + + b.Property("PrivateChatId") + .HasColumnType("char(36)"); + + b.Property("RecipientId") + .HasColumnType("char(36)"); + + b.Property("RecipientType") + .HasColumnType("int"); + + b.Property("ReplyToMessageId") + .HasColumnType("char(36)"); + + b.Property("SenderId") + .HasColumnType("char(36)"); + + b.Property("SentAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("ChatGroupId"); + + b.HasIndex("PrivateChatId"); + + b.ToTable("Messages"); + }); + + modelBuilder.Entity("Govor.Core.Models.Messages.MessageReaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("MessageId") + .HasColumnType("char(36)"); + + b.Property("ReactedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReactionCode") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("varchar(64)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("MessageId", "UserId") + .IsUnique(); + + b.ToTable("MessageReactions"); + }); + + modelBuilder.Entity("Govor.Core.Models.Messages.MessageView", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("MessageId") + .HasColumnType("char(36)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.Property("ViewedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("MessageId", "UserId") + .IsUnique(); + + b.ToTable("MessageViews"); + }); + + modelBuilder.Entity("Govor.Core.Models.PrivateChat", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("UserAId") + .HasColumnType("char(36)"); + + b.Property("UserBId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("PrivateChats"); + }); + + modelBuilder.Entity("Govor.Core.Models.Users.Admin", b => + { + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("UserId"); + + b.ToTable("Admins"); + }); + + modelBuilder.Entity("Govor.Core.Models.Users.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedOn") + .HasColumnType("date"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IconId") + .HasColumnType("char(36)"); + + b.Property("InviteId") + .HasColumnType("char(36)"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Username") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("WasOnline") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("InviteId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Govor.Core.Models.Friendship", b => + { + b.HasOne("Govor.Core.Models.Users.User", "Addressee") + .WithMany("ReceivedFriendRequests") + .HasForeignKey("AddresseeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Govor.Core.Models.Users.User", "Requester") + .WithMany("SentFriendRequests") + .HasForeignKey("RequesterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Addressee"); + + b.Navigation("Requester"); + }); + + modelBuilder.Entity("Govor.Core.Models.GroupAdmins", b => + { + b.HasOne("Govor.Core.Models.ChatGroup", null) + .WithMany("Admins") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Govor.Core.Models.GroupInvitation", b => + { + b.HasOne("Govor.Core.Models.ChatGroup", null) + .WithMany("InviteCodes") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Govor.Core.Models.Users.User", "UserMaker") + .WithMany() + .HasForeignKey("UserMakerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("UserMaker"); + }); + + modelBuilder.Entity("Govor.Core.Models.GroupMembership", b => + { + b.HasOne("Govor.Core.Models.ChatGroup", null) + .WithMany("Members") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Govor.Core.Models.GroupInvitation", null) + .WithMany() + .HasForeignKey("InvitationId") + .OnDelete(DeleteBehavior.SetNull) + .IsRequired(); + }); + + modelBuilder.Entity("Govor.Core.Models.Messages.MediaAttachments", b => + { + b.HasOne("Govor.Core.Models.MediaFile", "MediaFile") + .WithMany() + .HasForeignKey("MediaFileId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Govor.Core.Models.Messages.Message", "Message") + .WithMany("MediaAttachments") + .HasForeignKey("MessageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MediaFile"); + + b.Navigation("Message"); + }); + + modelBuilder.Entity("Govor.Core.Models.Messages.Message", b => + { + b.HasOne("Govor.Core.Models.ChatGroup", null) + .WithMany("Messages") + .HasForeignKey("ChatGroupId"); + + b.HasOne("Govor.Core.Models.PrivateChat", null) + .WithMany("Messages") + .HasForeignKey("PrivateChatId"); + }); + + modelBuilder.Entity("Govor.Core.Models.Messages.MessageReaction", b => + { + b.HasOne("Govor.Core.Models.Messages.Message", "Message") + .WithMany("Reactions") + .HasForeignKey("MessageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Govor.Core.Models.Users.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Message"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Govor.Core.Models.Messages.MessageView", b => + { + b.HasOne("Govor.Core.Models.Messages.Message", null) + .WithMany("MessageViews") + .HasForeignKey("MessageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Govor.Core.Models.Users.Admin", b => + { + b.HasOne("Govor.Core.Models.Users.User", "User") + .WithOne() + .HasForeignKey("Govor.Core.Models.Users.Admin", "UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Govor.Core.Models.Users.User", b => + { + b.HasOne("Govor.Core.Models.Invitation", "Invite") + .WithMany("Users") + .HasForeignKey("InviteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Invite"); + }); + + modelBuilder.Entity("Govor.Core.Models.ChatGroup", b => + { + b.Navigation("Admins"); + + b.Navigation("InviteCodes"); + + b.Navigation("Members"); + + b.Navigation("Messages"); + }); + + modelBuilder.Entity("Govor.Core.Models.Invitation", b => + { + b.Navigation("Users"); + }); + + modelBuilder.Entity("Govor.Core.Models.Messages.Message", b => + { + b.Navigation("MediaAttachments"); + + b.Navigation("MessageViews"); + + b.Navigation("Reactions"); + }); + + modelBuilder.Entity("Govor.Core.Models.PrivateChat", b => + { + b.Navigation("Messages"); + }); + + modelBuilder.Entity("Govor.Core.Models.Users.User", b => + { + b.Navigation("ReceivedFriendRequests"); + + b.Navigation("SentFriendRequests"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Govor.Data/Migrations/20250713101943_AddUploaderIdToMediaFile.cs b/Govor.Data/Migrations/20250713101943_AddUploaderIdToMediaFile.cs new file mode 100644 index 0000000..db02898 --- /dev/null +++ b/Govor.Data/Migrations/20250713101943_AddUploaderIdToMediaFile.cs @@ -0,0 +1,31 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Govor.Data.Migrations +{ + /// + public partial class AddUploaderIdToMediaFile : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "UploaderId", + table: "MediaFiles", + type: "char(36)", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + collation: "ascii_general_ci"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "UploaderId", + table: "MediaFiles"); + } + } +} diff --git a/Govor.Data/Migrations/GovorDbContextModelSnapshot.cs b/Govor.Data/Migrations/GovorDbContextModelSnapshot.cs index 6937a5e..33d36a5 100644 --- a/Govor.Data/Migrations/GovorDbContextModelSnapshot.cs +++ b/Govor.Data/Migrations/GovorDbContextModelSnapshot.cs @@ -215,6 +215,9 @@ namespace Govor.Data.Migrations .HasMaxLength(128) .HasColumnType("varchar(128)"); + b.Property("UploaderId") + .HasColumnType("char(36)"); + b.Property("Url") .IsRequired() .HasColumnType("longtext");