Call user presence
This commit is contained in:
parent
81aa0ec1c0
commit
d84d7631ce
6 changed files with 154 additions and 12 deletions
|
@ -71,10 +71,17 @@ public class UserApiController : BaseApiController
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Receive a user's location heartbeat.
|
/// Receive a user's location heartbeat.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="heartbeatModel">Instance of <see cref="HeartbeatRootRequestModel"/>.</param>
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[HttpPut("location")]
|
[HttpPut("location")]
|
||||||
public async Task<IActionResult> ReceiveLocation([FromBody] HeartbeatRootRequestModel heartbeatModel)
|
public async Task<IActionResult> ReceiveLocation([FromBody] HeartbeatRootRequestModel heartbeatModel)
|
||||||
{
|
{
|
||||||
|
if (heartbeatModel?.Location != null)
|
||||||
|
{
|
||||||
|
var heartbeatDto = heartbeatModel.Location.ToUserLocationHeartbeat();
|
||||||
|
await _userPresenceService.ConsumeLocationHeartbeat(heartbeatDto);
|
||||||
|
}
|
||||||
|
|
||||||
return SuccessResult();
|
return SuccessResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
// </copyright>
|
// </copyright>
|
||||||
|
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
using Astral.Services.Dtos;
|
||||||
|
|
||||||
namespace Astral.ApiServer.Models;
|
namespace Astral.ApiServer.Models;
|
||||||
|
|
||||||
|
@ -15,7 +16,7 @@ public class HeartbeatModel
|
||||||
/// True if connected.
|
/// True if connected.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonPropertyName("connected")]
|
[JsonPropertyName("connected")]
|
||||||
public bool Connected { get; set; }
|
public bool? Connected { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Path.
|
/// Path.
|
||||||
|
@ -27,13 +28,13 @@ public class HeartbeatModel
|
||||||
/// Domain id.
|
/// Domain id.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonPropertyName("domain_id")]
|
[JsonPropertyName("domain_id")]
|
||||||
public Guid DomainId { get; set; }
|
public string DomainId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Place id.
|
/// Place id.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonPropertyName("place_id")]
|
[JsonPropertyName("place_id")]
|
||||||
public Guid PlaceId { get; set; }
|
public string PlaceId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Network address.
|
/// Network address.
|
||||||
|
@ -45,17 +46,71 @@ public class HeartbeatModel
|
||||||
/// Network port.
|
/// Network port.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonPropertyName("network_port")]
|
[JsonPropertyName("network_port")]
|
||||||
public int NetworkPort { get; set; }
|
public int? NetworkPort { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Node id.
|
/// Node id.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonPropertyName("node_id")]
|
[JsonPropertyName("node_id")]
|
||||||
public Guid NodeId { get; set; }
|
public string NodeId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Availability.
|
/// Availability.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonPropertyName("availability")]
|
[JsonPropertyName("availability")]
|
||||||
public string Availability { get; set; }
|
public string Availability { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert this model to <see cref="UserLocationHeartbeatDto"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Instance of <see cref="UserLocationHeartbeatDto"/>.</returns>
|
||||||
|
public UserLocationHeartbeatDto ToUserLocationHeartbeat()
|
||||||
|
{
|
||||||
|
var result = new UserLocationHeartbeatDto()
|
||||||
|
{
|
||||||
|
Connected = Connected,
|
||||||
|
Path = Path,
|
||||||
|
NetworkAddress = NetworkAddress,
|
||||||
|
NetworkPort = NetworkPort,
|
||||||
|
Availability = Availability
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(DomainId))
|
||||||
|
{
|
||||||
|
if (Guid.TryParse(DomainId, out var guid))
|
||||||
|
{
|
||||||
|
result.DomainId = guid;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result.DomainId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(PlaceId))
|
||||||
|
{
|
||||||
|
if (Guid.TryParse(PlaceId, out var guid))
|
||||||
|
{
|
||||||
|
result.PlaceId = guid;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PlaceId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(NodeId))
|
||||||
|
{
|
||||||
|
if (Guid.TryParse(NodeId, out var guid))
|
||||||
|
{
|
||||||
|
result.NodeId = guid;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result.NodeId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ public class UserLocationHeartbeatDto
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// True if connected.
|
/// True if connected.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Connected { get; set; }
|
public bool? Connected { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Path.
|
/// Path.
|
||||||
|
@ -22,12 +22,12 @@ public class UserLocationHeartbeatDto
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Domain id.
|
/// Domain id.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Guid DomainId { get; set; }
|
public Guid? DomainId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Place id.
|
/// Place id.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Guid PlaceId { get; set; }
|
public Guid? PlaceId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Network address.
|
/// Network address.
|
||||||
|
@ -37,12 +37,12 @@ public class UserLocationHeartbeatDto
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Network port.
|
/// Network port.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int NetworkPort { get; set; }
|
public int? NetworkPort { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Node id.
|
/// Node id.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Guid NodeId { get; set; }
|
public Guid? NodeId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Availability.
|
/// Availability.
|
||||||
|
|
37
Astral.Services/Helpers/EntityHelpers.cs
Normal file
37
Astral.Services/Helpers/EntityHelpers.cs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
// <copyright file="EntityHelpers.cs" company="alveus.dev">
|
||||||
|
// Copyright (c) alveus.dev. All rights reserved. Licensed under the MIT License.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
namespace Astral.Services.Helpers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Entity helpers.
|
||||||
|
/// </summary>
|
||||||
|
public static class EntityHelpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Apply changes only when new entity property is different and not null.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="oldEntity">Old entity to compare against.</param>
|
||||||
|
/// <param name="newEntity">New entity with new values.</param>
|
||||||
|
/// <typeparam name="TEntity">The type of entity to compare.</typeparam>
|
||||||
|
/// <returns>The new entity with changes applied.</returns>
|
||||||
|
public static TEntity ApplyChanges<TEntity>(TEntity oldEntity, TEntity newEntity)
|
||||||
|
{
|
||||||
|
var entityType = typeof(TEntity);
|
||||||
|
var properties = entityType.GetProperties();
|
||||||
|
foreach (var property in properties)
|
||||||
|
{
|
||||||
|
if (property.GetValue(newEntity) != null && property.GetValue(newEntity) != property.GetValue(oldEntity))
|
||||||
|
{
|
||||||
|
property.SetValue(oldEntity, property.GetValue(newEntity));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
property.SetValue(oldEntity, property.GetValue(oldEntity));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return oldEntity;
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@
|
||||||
// Copyright (c) alveus.dev. All rights reserved. Licensed under the MIT License.
|
// Copyright (c) alveus.dev. All rights reserved. Licensed under the MIT License.
|
||||||
// </copyright>
|
// </copyright>
|
||||||
|
|
||||||
|
using Astral.Core.Constants;
|
||||||
using Astral.Core.Entities;
|
using Astral.Core.Entities;
|
||||||
using Astral.Services.Dtos;
|
using Astral.Services.Dtos;
|
||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
|
@ -31,5 +32,20 @@ public class AutomapperProfile : Profile
|
||||||
.ForMember(dest => dest.Title, opt => opt.MapFrom(src => src.Title))
|
.ForMember(dest => dest.Title, opt => opt.MapFrom(src => src.Title))
|
||||||
.ForMember(dest => dest.Description, opt => opt.MapFrom(src => src.Description))
|
.ForMember(dest => dest.Description, opt => opt.MapFrom(src => src.Description))
|
||||||
.ForMember(dest => dest.Internal, opt => opt.MapFrom(src => src.Internal));
|
.ForMember(dest => dest.Internal, opt => opt.MapFrom(src => src.Internal));
|
||||||
|
|
||||||
|
CreateMap<UserLocationHeartbeatDto, UserPresence>()
|
||||||
|
.ForMember(
|
||||||
|
dest => dest.Availability,
|
||||||
|
opt => opt.MapFrom((src, dest) =>
|
||||||
|
Enum.TryParse(src.Availability, true, out Discoverability discoverability)
|
||||||
|
? discoverability
|
||||||
|
: Discoverability.None))
|
||||||
|
.ForMember(dest => dest.Connected, opt => opt.MapFrom(src => src.Connected))
|
||||||
|
.ForMember(dest => dest.Path, opt => opt.MapFrom(src => src.Path))
|
||||||
|
.ForMember(dest => dest.DomainId, opt => opt.MapFrom(src => src.DomainId))
|
||||||
|
.ForMember(dest => dest.NetworkAddress, opt => opt.MapFrom(src => src.NetworkAddress))
|
||||||
|
.ForMember(dest => dest.NetworkPort, opt => opt.MapFrom(src => src.NetworkPort))
|
||||||
|
.ForMember(dest => dest.NodeId, opt => opt.MapFrom(src => src.NodeId))
|
||||||
|
.ForMember(dest => dest.PlaceId, opt => opt.MapFrom(src => src.PlaceId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,9 @@ using Astral.Core.RepositoryInterfaces;
|
||||||
using Astral.Services.Constants;
|
using Astral.Services.Constants;
|
||||||
using Astral.Services.Dtos;
|
using Astral.Services.Dtos;
|
||||||
using Astral.Services.Exceptions;
|
using Astral.Services.Exceptions;
|
||||||
|
using Astral.Services.Helpers;
|
||||||
using Astral.Services.Interfaces;
|
using Astral.Services.Interfaces;
|
||||||
|
using AutoMapper;
|
||||||
using Injectio.Attributes;
|
using Injectio.Attributes;
|
||||||
|
|
||||||
namespace Astral.Services.Services;
|
namespace Astral.Services.Services;
|
||||||
|
@ -20,6 +22,7 @@ public class UserPresenceService : IUserPresenceService
|
||||||
private readonly IIdentityProvider _identityProvider;
|
private readonly IIdentityProvider _identityProvider;
|
||||||
private readonly ICryptographyService _cryptographyService;
|
private readonly ICryptographyService _cryptographyService;
|
||||||
private readonly IUserPresenceRepository _userPresenceRepository;
|
private readonly IUserPresenceRepository _userPresenceRepository;
|
||||||
|
private readonly IMapper _mapper;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="UserPresenceService"/> class.
|
/// Initializes a new instance of the <see cref="UserPresenceService"/> class.
|
||||||
|
@ -27,14 +30,17 @@ public class UserPresenceService : IUserPresenceService
|
||||||
/// <param name="identityProvider">Instance of <see cref="IIdentityProvider"/>.</param>
|
/// <param name="identityProvider">Instance of <see cref="IIdentityProvider"/>.</param>
|
||||||
/// <param name="cryptographyService">Instance of <see cref="ICryptographyService"/>.</param>
|
/// <param name="cryptographyService">Instance of <see cref="ICryptographyService"/>.</param>
|
||||||
/// <param name="userPresenceRepository">Instance of <see cref="IUserPresenceRepository"/>.</param>
|
/// <param name="userPresenceRepository">Instance of <see cref="IUserPresenceRepository"/>.</param>
|
||||||
|
/// <param name="mapper">Instance of <see cref="IMapper"/>.</param>
|
||||||
public UserPresenceService(
|
public UserPresenceService(
|
||||||
IIdentityProvider identityProvider,
|
IIdentityProvider identityProvider,
|
||||||
ICryptographyService cryptographyService,
|
ICryptographyService cryptographyService,
|
||||||
IUserPresenceRepository userPresenceRepository)
|
IUserPresenceRepository userPresenceRepository,
|
||||||
|
IMapper mapper)
|
||||||
{
|
{
|
||||||
_identityProvider = identityProvider;
|
_identityProvider = identityProvider;
|
||||||
_cryptographyService = cryptographyService;
|
_cryptographyService = cryptographyService;
|
||||||
_userPresenceRepository = userPresenceRepository;
|
_userPresenceRepository = userPresenceRepository;
|
||||||
|
_mapper = mapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -52,6 +58,7 @@ public class UserPresenceService : IUserPresenceService
|
||||||
{
|
{
|
||||||
heartbeat = new UserPresence()
|
heartbeat = new UserPresence()
|
||||||
{
|
{
|
||||||
|
CreatedAt = DateTime.UtcNow,
|
||||||
Id = userId,
|
Id = userId,
|
||||||
LastHeartbeat = DateTime.UtcNow
|
LastHeartbeat = DateTime.UtcNow
|
||||||
};
|
};
|
||||||
|
@ -68,7 +75,27 @@ public class UserPresenceService : IUserPresenceService
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task ConsumeLocationHeartbeat(UserLocationHeartbeatDto heartbeat)
|
public async Task ConsumeLocationHeartbeat(UserLocationHeartbeatDto heartbeat)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
var userId = _identityProvider.GetUserId();
|
||||||
|
|
||||||
|
if (userId == Guid.Empty)
|
||||||
|
{
|
||||||
|
throw new UnauthorizedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
var newHeartbeat = _mapper.Map<UserPresence>(heartbeat);
|
||||||
|
newHeartbeat.Id = userId;
|
||||||
|
|
||||||
|
var oldHeartbeat = await _userPresenceRepository.FindByIdAsync(userId) ?? new UserPresence()
|
||||||
|
{
|
||||||
|
CreatedAt = DateTime.UtcNow,
|
||||||
|
Id = userId
|
||||||
|
};
|
||||||
|
newHeartbeat.CreatedAt = oldHeartbeat.CreatedAt;
|
||||||
|
newHeartbeat.UpdatedAt = oldHeartbeat.UpdatedAt;
|
||||||
|
newHeartbeat.LastHeartbeat = oldHeartbeat.LastHeartbeat;
|
||||||
|
newHeartbeat = EntityHelpers.ApplyChanges(oldHeartbeat, newHeartbeat);
|
||||||
|
|
||||||
|
await _userPresenceRepository.UpdateAsync(newHeartbeat);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
Loading…
Reference in a new issue