mirror of
https://github.com/Govor-team/Govor.git
synced 2026-07-21 19:54:55 +00:00
Add user online presence tracking and notification
Introduced interfaces and services for tracking user online status, including IOnlineUserStore, IUserNotificationScopeService, and IUserPresenceReader. Added PresenceHub for real-time presence updates via SignalR. Updated OnlinePingingController to use new services and return online status and last seen. Extended UserDto with IsOnline property. Updated dependency injection and privacy settings enum. Removed obsolete IUserPresenceService.
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
using Govor.Application.Interfaces;
|
using Govor.Application.Interfaces;
|
||||||
using Govor.Application.Interfaces.Infrastructure.Extensions;
|
using Govor.Application.Interfaces.Infrastructure.Extensions;
|
||||||
using Govor.Core.Repositories.Users;
|
using Govor.Application.Interfaces.UserOnlineStatus;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
@@ -13,7 +13,8 @@ public class OnlinePingingController : Controller
|
|||||||
{
|
{
|
||||||
private readonly ILogger<OnlinePingingController> _logger;
|
private readonly ILogger<OnlinePingingController> _logger;
|
||||||
private readonly IPingHandlerService _ping;
|
private readonly IPingHandlerService _ping;
|
||||||
private readonly IUserPresenceService _presenceService;
|
private readonly IUserPresenceReader _presenceReader;
|
||||||
|
private readonly IOnlineUserStore _userOnlineStore;
|
||||||
private readonly ICurrentUserService _currentUserService;
|
private readonly ICurrentUserService _currentUserService;
|
||||||
|
|
||||||
public OnlinePingingController(ILogger<OnlinePingingController> logger,
|
public OnlinePingingController(ILogger<OnlinePingingController> logger,
|
||||||
@@ -57,11 +58,17 @@ public class OnlinePingingController : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("status/{userId}")]
|
[HttpGet("status/{userId}")]
|
||||||
public IActionResult GetStatus(Guid userId)
|
public async Task<IActionResult> GetStatus(Guid userId)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return Ok(_presenceService.WhenUserWasOnline(userId));
|
var isOnline = _userOnlineStore.IsOnline(userId);
|
||||||
|
var lastSeen = await _presenceReader.GetLastSeenAsync(userId);
|
||||||
|
|
||||||
|
return Ok(new {
|
||||||
|
isOnline,
|
||||||
|
lastSeen
|
||||||
|
});
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,12 +7,14 @@ using Govor.Application.Interfaces.Friends;
|
|||||||
using Govor.Application.Interfaces.Infrastructure.Extensions;
|
using Govor.Application.Interfaces.Infrastructure.Extensions;
|
||||||
using Govor.Application.Interfaces.Medias;
|
using Govor.Application.Interfaces.Medias;
|
||||||
using Govor.Application.Interfaces.Messages;
|
using Govor.Application.Interfaces.Messages;
|
||||||
|
using Govor.Application.Interfaces.UserOnlineStatus;
|
||||||
using Govor.Application.Interfaces.UserSession;
|
using Govor.Application.Interfaces.UserSession;
|
||||||
using Govor.Application.Services;
|
using Govor.Application.Services;
|
||||||
using Govor.Application.Services.Authentication;
|
using Govor.Application.Services.Authentication;
|
||||||
using Govor.Application.Services.Friends;
|
using Govor.Application.Services.Friends;
|
||||||
using Govor.Application.Services.Medias;
|
using Govor.Application.Services.Medias;
|
||||||
using Govor.Application.Services.Messages;
|
using Govor.Application.Services.Messages;
|
||||||
|
using Govor.Application.Services.UserOnlineStatus;
|
||||||
using Govor.Application.Services.UserSessions;
|
using Govor.Application.Services.UserSessions;
|
||||||
using Govor.Core.Infrastructure.Extensions;
|
using Govor.Core.Infrastructure.Extensions;
|
||||||
using Govor.Core.Infrastructure.Validators;
|
using Govor.Core.Infrastructure.Validators;
|
||||||
@@ -74,6 +76,11 @@ public static class ConfigurationProgramExtensions
|
|||||||
// UserSession
|
// UserSession
|
||||||
services.AddScoped<IUserSessionOpener, UserSessionOpener>();
|
services.AddScoped<IUserSessionOpener, UserSessionOpener>();
|
||||||
services.AddScoped<IUserSessionRefresher, UserSessionRefresher>();
|
services.AddScoped<IUserSessionRefresher, UserSessionRefresher>();
|
||||||
|
|
||||||
|
services.AddScoped<IUserNotificationScopeService, UserNotificationScopeService>();
|
||||||
|
services.AddScoped<IUserPresenceReader, UserPresenceReader>();
|
||||||
|
services.AddSingleton<IOnlineUserStore, OnlineUserStore>();
|
||||||
|
|
||||||
// Auto Mapper
|
// Auto Mapper
|
||||||
services.AddAutoMapper(typeof(MappingProfile));
|
services.AddAutoMapper(typeof(MappingProfile));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
using Govor.Application.Interfaces;
|
||||||
|
using Govor.Application.Interfaces.Infrastructure.Extensions;
|
||||||
|
using Govor.Application.Interfaces.UserOnlineStatus;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
|
||||||
|
namespace Govor.API.Hubs;
|
||||||
|
|
||||||
|
[Authorize(Roles = "Admin, User")]
|
||||||
|
[Route("hubs/presence")]
|
||||||
|
public class PresenceHub : Hub
|
||||||
|
{
|
||||||
|
private readonly ILogger<PresenceHub> _logger;
|
||||||
|
private readonly IUserNotificationScopeService _notificationScopeService;
|
||||||
|
private readonly IOnlineUserStore _onlineUserStore;
|
||||||
|
|
||||||
|
public PresenceHub(ILogger<PresenceHub> logger, IUserNotificationScopeService notificationScopeService, IOnlineUserStore onlineUserStore)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_notificationScopeService = notificationScopeService;
|
||||||
|
_onlineUserStore = onlineUserStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task OnConnectedAsync()
|
||||||
|
{
|
||||||
|
var userId = GetUserId();
|
||||||
|
if (userId == Guid.Empty)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("User connected with invalid UserID claim.");
|
||||||
|
Context.Abort();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_onlineUserStore.SetOnlineUser(userId);
|
||||||
|
await Groups.AddToGroupAsync(Context.ConnectionId, userId.ToString());
|
||||||
|
|
||||||
|
var friends = await _notificationScopeService.GetNotifiedUsers(userId);
|
||||||
|
|
||||||
|
foreach (var recipient in friends)
|
||||||
|
{
|
||||||
|
await Clients.Group(recipient.ToString())
|
||||||
|
.SendAsync("UserOnline", userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
await base.OnConnectedAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task OnDisconnectedAsync(Exception? exception)
|
||||||
|
{
|
||||||
|
var userId = GetUserId();
|
||||||
|
if (userId == Guid.Empty) return;
|
||||||
|
|
||||||
|
_onlineUserStore.SetOfflineUser(userId);
|
||||||
|
|
||||||
|
var friends = await _notificationScopeService.GetNotifiedUsers(userId);
|
||||||
|
|
||||||
|
foreach (var recipient in friends)
|
||||||
|
{
|
||||||
|
await Clients.Group(recipient.ToString())
|
||||||
|
.SendAsync("UserOffline", userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
await base.OnDisconnectedAsync(exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
using Govor.Application.Services.UserOnlineStatus;
|
||||||
|
|
||||||
|
namespace Govor.Application.Tests.Services.UserOnlineStatus;
|
||||||
|
|
||||||
|
[TestFixture]
|
||||||
|
[TestOf(typeof(UserNotificationScopeService))]
|
||||||
|
public class UserNotificationScopeServiceTests
|
||||||
|
{
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void METHOD()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
namespace Govor.Application.Interfaces;
|
|
||||||
|
|
||||||
public interface IUserPresenceService
|
|
||||||
{
|
|
||||||
DateTime WhenUserWasOnline(Guid userId);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Govor.Application.Interfaces.UserOnlineStatus;
|
||||||
|
|
||||||
|
public interface IOnlineUserStore
|
||||||
|
{
|
||||||
|
void SetOnlineUser(Guid userId);
|
||||||
|
void SetOfflineUser(Guid userId);
|
||||||
|
bool IsOnline(Guid userId);
|
||||||
|
IReadOnlyCollection<Guid> GetAllOnlineUsers();
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Govor.Application.Interfaces.UserOnlineStatus;
|
||||||
|
|
||||||
|
public interface IUserNotificationScopeService
|
||||||
|
{
|
||||||
|
Task<List<Guid>> GetNotifiedUsers(Guid userId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Govor.Application.Interfaces.UserOnlineStatus;
|
||||||
|
|
||||||
|
public interface IUserPresenceReader
|
||||||
|
{
|
||||||
|
Task<DateTime?> GetLastSeenAsync(Guid userId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using Govor.Application.Interfaces.UserOnlineStatus;
|
||||||
|
|
||||||
|
namespace Govor.Application.Services.UserOnlineStatus;
|
||||||
|
|
||||||
|
public class OnlineUserStore : IOnlineUserStore
|
||||||
|
{
|
||||||
|
public void SetOnlineUser(Guid userId)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetOfflineUser(Guid userId)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsOnline(Guid userId)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyCollection<Guid> GetAllOnlineUsers()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
using Govor.Application.Interfaces.Friends;
|
||||||
|
using Govor.Application.Interfaces.UserOnlineStatus;
|
||||||
|
using Govor.Data.Repositories.Exceptions;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Govor.Application.Services.UserOnlineStatus;
|
||||||
|
|
||||||
|
public class UserNotificationScopeService : IUserNotificationScopeService
|
||||||
|
{
|
||||||
|
private readonly ILogger<UserNotificationScopeService> _logger;
|
||||||
|
private readonly IFriendshipService _friendships;
|
||||||
|
|
||||||
|
public UserNotificationScopeService(ILogger<UserNotificationScopeService> logger, IFriendshipService friendships)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_friendships = friendships;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<Guid>> GetNotifiedUsers(Guid userId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"Getting notified users of online/offline action user {userId}");
|
||||||
|
var users = await _friendships.GetFriendsAsync(userId);
|
||||||
|
return users.Select(u => u.Id).ToList();
|
||||||
|
}
|
||||||
|
catch (NotFoundByKeyException<Guid> ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, ex.Message);
|
||||||
|
throw new InvalidOperationException("User not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*public async Task SetWasOnlineAsync(Guid userId, DateTime when)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Set was-online for user {UserId}", userId);
|
||||||
|
var user = await _users.FindByIdAsync(userId);
|
||||||
|
user.WasOnline = when;
|
||||||
|
await _users.UpdateAsync(user);
|
||||||
|
}
|
||||||
|
catch (NotFoundByKeyException<Guid> ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, ex.Message);
|
||||||
|
throw new InvalidOperationException("User not found");
|
||||||
|
}
|
||||||
|
catch (UpdateException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to set WasOnline for user {UserId} at {Time}", userId, when);
|
||||||
|
throw new InvalidOperationException("Something went wrong when trying to set was-online for user");
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using Govor.Application.Interfaces.UserOnlineStatus;
|
||||||
|
|
||||||
|
namespace Govor.Application.Services.UserOnlineStatus;
|
||||||
|
|
||||||
|
public class UserPresenceReader : IUserPresenceReader
|
||||||
|
{
|
||||||
|
public Task<DateTime?> GetLastSeenAsync(Guid userId)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,4 +7,5 @@ public class UserDto
|
|||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
public DateTime WasOnline { get; set; }
|
public DateTime WasOnline { get; set; }
|
||||||
public Guid IconId {get; set;}
|
public Guid IconId {get; set;}
|
||||||
|
public bool IsOnline { get; set; }
|
||||||
}
|
}
|
||||||
@@ -34,7 +34,8 @@ public enum PrivacyTargetArea
|
|||||||
{
|
{
|
||||||
CanSend = 0,
|
CanSend = 0,
|
||||||
CanSeeTimeWas = 1,
|
CanSeeTimeWas = 1,
|
||||||
CanSeeImage = 2
|
CanSeeImage = 2,
|
||||||
|
CanSendImage = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum PrivacyRuleType
|
public enum PrivacyRuleType
|
||||||
|
|||||||
Reference in New Issue
Block a user