app.MapHub<FriendsHub>("/api/friends");

This commit is contained in:
Artemy
2025-07-11 12:19:32 +07:00
parent d9dfaff079
commit e8e2078514
9 changed files with 249 additions and 325 deletions
+1
View File
@@ -8,6 +8,7 @@ COPY Govor.API/*.csproj ./Govor.API/
COPY Govor.Application/*.csproj ./Govor.Application/
COPY Govor.Core/*.csproj ./Govor.Core/
COPY Govor.Data/*.csproj ./Govor.Data/
COPY Govor.Contracts/*.csproj ./Govor.Contracts/
RUN dotnet restore
# Копируем все исходники и билдим проект в Release режиме
@@ -14,6 +14,7 @@ namespace Govor.API.Tests.IntegrationTests.Controllers;
[TestFixture]
public class FriendsControllerTests
{
/*
private Fixture _fixture;
private Mock<ILogger<FriendsController>> _loggerMock;
private Mock<IFriendsService> _friendsServiceMock;
@@ -37,7 +38,6 @@ public class FriendsControllerTests
_currentUserServiceMock.Object
);
}
// Tests for Search action
[Test]
public async Task Search_ValidRequest_ReturnsOkResult()
@@ -376,9 +376,11 @@ public class FriendsControllerTests
Assert.That(objectResult.StatusCode, Is.EqualTo(500));
}
[TearDown]
public void TearDown()
{
_controller.Dispose();
}
*/
}
@@ -1,212 +0,0 @@
using Govor.Application.Exceptions.FriendsService;
using Govor.Application.Interfaces;
using Govor.Application.Interfaces.Infrastructure.Extensions;
using Govor.Contracts.DTOs;
using Govor.Core.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Govor.API.Controllers;
[ApiController]
[Route("api/[controller]")]
[Authorize(Roles = "User,Admin")]
public class FriendsController : Controller
{
private readonly ILogger<FriendsController> _logger;
private readonly IFriendsService _friendsService;
private readonly ICurrentUserService _currentUserService;
public FriendsController(ILogger<FriendsController> logger,
IFriendsService friendsService,
ICurrentUserService currentUserService)
{
_logger = logger;
_friendsService = friendsService;
_currentUserService = currentUserService;
}
[HttpGet("search")] // api/friends/search?query=
public async Task<IActionResult> Search(string query)
{
if (string.IsNullOrWhiteSpace(query))
return BadRequest("Query cannot be empty");
try
{
var result = await _friendsService.SearchUsersAsync(query, _currentUserService.GetCurrentUserId());
return Ok(BuildUserDtos(result));
}
catch (SearchUsersException ex)
{
_logger.LogWarning(ex, ex.Message);
return NotFound(new { error = ex.Message });
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
return StatusCode(500, new { error = "Internal error during user search." });
}
}
[HttpPost("request")]
public async Task<IActionResult> SendRequest(Guid targetUserId)
{
if (targetUserId == Guid.Empty)
return BadRequest("Target user ID is invalid");
try
{
await _friendsService.SendFriendRequestAsync(_currentUserService.GetCurrentUserId(), targetUserId);
return Ok(new { message = "Friend request sent successfully." });
}
catch (InvalidOperationException ex)
{
_logger.LogWarning(ex, "Tried to send request to self");
return UnprocessableEntity(new { error = ex.Message });
}
catch (RequestAlreadySentException ex)
{
_logger.LogWarning(ex, ex.Message);
return Conflict(new { error = ex.Message });
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
return StatusCode(500, new { error = "Failed to send friend request." });
}
}
[HttpGet("requests")]
public async Task<IActionResult> GetIncomingRequests()
{
try
{
var result = await _friendsService.GetIncomingRequestsAsync(_currentUserService.GetCurrentUserId());
return Ok(BuildFriendshipDtos(result));
}
catch (InvalidOperationException ex)
{
_logger.LogError(ex, ex.Message);
return BadRequest("Failed to get friend requests. User data missing.");
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
return StatusCode(500, new { error = "Internal server error." });
}
}
[HttpGet("responses")]
public async Task<IActionResult> GetResponses()
{
try
{
var result = await _friendsService.GetResponsesAsync(_currentUserService.GetCurrentUserId());
return Ok(BuildFriendshipDtos(result));
}
catch (InvalidOperationException ex)
{
_logger.LogError(ex, ex.Message);
return Ok(Array.Empty<FriendshipDto>());
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
return StatusCode(500, new { error = "Internal server error." });
}
}
[HttpPost("accept")]
public async Task<IActionResult> AcceptFriend([FromQuery] Guid friendshipId)
{
if (friendshipId == Guid.Empty)
return BadRequest("Requester ID is invalid");
try
{
await _friendsService.AcceptFriendRequestAsync(friendshipId, _currentUserService.GetCurrentUserId());
return Ok(new { message = "Friend request accepted." });
}
catch (InvalidOperationException ex)
{
_logger.LogWarning(ex, ex.Message);
return NotFound(new { error = ex.Message });
}
catch (UnauthorizedAccessException ex)
{
_logger.LogWarning(ex, ex.Message);
return Forbid();
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
return StatusCode(500, new { error = "Failed to accept friend request." });
}
}
[HttpPost("reject")]
public async Task<IActionResult> RejectFriend([FromQuery] Guid friendshipId)
{
if (friendshipId == Guid.Empty)
return BadRequest("Requester ID is invalid");
try
{
await _friendsService.RejectFriendRequestAsync(friendshipId, _currentUserService.GetCurrentUserId());
return Ok(new { message = "Friend request rejected." });
}
catch (InvalidOperationException ex)
{
_logger.LogWarning(ex, ex.Message);
return NotFound(new { error = ex.Message });
}
catch (UnauthorizedAccessException ex)
{
_logger.LogWarning(ex, ex.Message);
return Forbid();
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
return StatusCode(500, new { error = "Failed to accept friend request." });
}
}
[HttpGet]
public async Task<IActionResult> GetFriends()
{
try
{
var result = await _friendsService.GetFriendsAsync(_currentUserService.GetCurrentUserId());
return Ok(BuildUserDtos(result));
}
catch (InvalidOperationException ex)
{
_logger.LogError(ex, ex.Message);
return Ok(Array.Empty<UserDto>());
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
return StatusCode(500, new { error = "Internal server error." });
}
}
private List<UserDto> BuildUserDtos(IEnumerable<User> users) => users.Select(user => new UserDto
{
Id = user.Id,
Username = user.Username,
Description = user.Description,
WasOnline = user.WasOnline,
IconId = user.IconId
}).ToList();
private List<FriendshipDto> BuildFriendshipDtos(IEnumerable<Friendship> friendships) => friendships.Select(f => new FriendshipDto
{
Id = f.Id,
Status = f.Status,
AddresseeId = f.AddresseeId,
RequesterId = f.RequesterId,
}).ToList();
}
@@ -1,5 +1,7 @@
using Govor.Application.Interfaces.Friends;
using Govor.Application.Interfaces.Infrastructure.Extensions;
using Govor.Contracts.DTOs;
using Govor.Core.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@@ -10,11 +12,11 @@ namespace Govor.API.Controllers.Friends;
[Route("api/friends")]
public class FriendsRequestQueryController : Controller
{
private readonly ILogger<FriendsController> _logger;
private readonly ILogger<FriendsRequestQueryController> _logger;
private readonly IFriendRequestQueryService _friendsService;
private readonly ICurrentUserService _currentUserService;
public FriendsRequestQueryController(ILogger<FriendsController> logger,
public FriendsRequestQueryController(ILogger<FriendsRequestQueryController> logger,
IFriendRequestQueryService friendsService,
ICurrentUserService currentUserService)
{
@@ -23,5 +25,51 @@ public class FriendsRequestQueryController : Controller
_currentUserService = currentUserService;
}
[HttpGet("requests")] // api/friends/requests
public async Task<IActionResult> GetIncomingRequests()
{
try
{
var result = await _friendsService.GetIncomingAsync(_currentUserService.GetCurrentUserId());
return Ok(BuildFriendshipDtos(result));
}
catch (InvalidOperationException ex)
{
_logger.LogError(ex, ex.Message);
return BadRequest("Failed to get friend requests. User data missing.");
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
return StatusCode(500, new { error = "Internal server error." });
}
}
[HttpGet("responses")]// api/friends/responses
public async Task<IActionResult> GetResponses()
{
try
{
var result = await _friendsService.GetResponsesAsync(_currentUserService.GetCurrentUserId());
return Ok(BuildFriendshipDtos(result));
}
catch (InvalidOperationException ex)
{
_logger.LogError(ex, ex.Message);
return Ok(Array.Empty<FriendshipDto>());
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
return StatusCode(500, new { error = "Internal server error." });
}
}
private List<FriendshipDto> BuildFriendshipDtos(IEnumerable<Friendship> friendships) => friendships.Select(f => new FriendshipDto
{
Id = f.Id,
Status = f.Status,
AddresseeId = f.AddresseeId,
RequesterId = f.RequesterId,
}).ToList();
}
@@ -10,12 +10,12 @@ namespace Govor.API.Controllers.Friends;
[Route("api/friends")]
public class FriendshipController : Controller
{
private readonly ILogger<FriendsController> _logger;
private readonly ILogger<FriendshipController> _logger;
private readonly IFriendshipService _friendsService;
//private readonly IUserDtoBuilder _builder;
private readonly ICurrentUserService _currentUserService;
public FriendshipController(ILogger<FriendsController> logger,
public FriendshipController(ILogger<FriendshipController> logger,
IFriendshipService friendsService,
ICurrentUserService currentUserService)
{
@@ -47,6 +47,26 @@ public class FriendshipController : Controller
}
}
[HttpGet] // api/friends
public async Task<IActionResult> GetFriends()
{
try
{
var result = await _friendsService.GetFriendsAsync(_currentUserService.GetCurrentUserId());
return Ok(BuildUserDtos(result));
}
catch (InvalidOperationException ex)
{
_logger.LogError(ex, ex.Message);
return Ok(Array.Empty<UserDto>());
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
return StatusCode(500, new { error = "Internal server error." });
}
}
private List<UserDto> BuildUserDtos(IEnumerable<User> users) => users.Select(user => new UserDto
{
Id = user.Id,
+63
View File
@@ -19,6 +19,52 @@ public class FriendsHub : Hub
_logger = logger;
}
public override async Task OnConnectedAsync()
{
var userId = GetUserId();
if (userId == Guid.Empty)
{
_logger.LogWarning("User connected with invalid UserID claim.");
Context.Abort(); // Abort connection if userID is invalid
return;
}
// Add user to their own group (for private messages and notifications)
await Groups.AddToGroupAsync(Context.ConnectionId, userId.ToString());
_logger.LogInformation("User {UserId} connected with ConnectionId {ConnectionId} and added to their group",
userId, Context.ConnectionId);
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception? exception)
{
var userId =
GetUserId(suppressException: true); // Suppress exception if userID is not found (e.g. connection aborted early)
if (userId != Guid.Empty)
{
// Remove user from their own group
await Groups.RemoveFromGroupAsync(Context.ConnectionId, userId.ToString());
_logger.LogInformation(
"User {UserId} disconnected with ConnectionId {ConnectionId} and removed from their group", userId,
Context.ConnectionId);
}
else if (exception != null)
{
_logger.LogWarning(exception,
"User disconnected with an exception and invalid UserID claim. ConnectionId: {ConnectionId}",
Context.ConnectionId);
}
else
{
_logger.LogInformation(
"User disconnected with no exception and invalid UserID claim. ConnectionId: {ConnectionId}",
Context.ConnectionId);
}
await base.OnDisconnectedAsync(exception);
}
public async Task<HubResult<object>> SendRequest(Guid targetUserId)
{
try
@@ -110,4 +156,21 @@ public class FriendsHub : Hub
return HubResult<object>.Error("Unexpected error! Please try later!");
}
}
private Guid GetUserId(bool suppressException = false)
{
var userIdClaim = Context.User?.FindFirst("userId")?.Value;
if (string.IsNullOrEmpty(userIdClaim) || !Guid.TryParse(userIdClaim, out var userId))
{
if (!suppressException)
{
_logger.LogError("Could not retrieve sender userId. Claim was: {UserIDClaim}", userIdClaim);
throw new UnauthorizedAccessException("userID claim is missing or invalid.");
}
return Guid.Empty;
}
return userId;
}
}
+2
View File
@@ -120,7 +120,9 @@ app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.MapHub<ChatsHub>("/api/chats");
app.MapHub<FriendsHub>("/api/friends");
app.MapSwagger().RequireAuthorization();
+1 -1
View File
@@ -28,7 +28,7 @@ namespace Govor.ConsoleClient
//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); // поменяй URL на свой
static HttpClientService HttpService = new(baseUrl);
private static FriendsClient? friendsClient;
static HubConnection? _hubConnection;
static Dictionary<string, List<string>> ChatHistory = new();
+1 -1
View File
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>