Files
Core_Be/Services/AdminClientDirectoryService.cs
T
Dhananjaya99 bffc9b1686 first commit
2026-09-28 11:44:26 +05:30

195 lines
7.5 KiB
C#

using Fitness_core_BE.Exceptions;
using Fitness_core_BE.Infra.UoW;
using Fitness_core_BE.Interfaces;
using Fitness_core_BE.Interfaces.Services;
using Fitness_core_BE.Models;
using Fitness_core_BE.Models.IOs;
using System.Security.Cryptography;
namespace Fitness_core_BE.Services
{
public class AdminClientDirectoryService : IAdminClientDirectoryService
{
private readonly IAdminClientDirectoryRepository _repository;
private readonly IAgentRepo _agentRepo;
private readonly IAccessDeviceRepo _accessDeviceRepo;
private readonly IPairingCodeRepo _pairingCodeRepo;
private readonly IUnitOfWork _UoW;
private readonly IAuthService _authService;
// Three missed 30-second health heartbeats marks an agent/device offline.
// Legacy agents without explicit health reports retain the old grace window.
private static readonly TimeSpan HealthOnlineWindow = TimeSpan.FromSeconds(90);
private static readonly TimeSpan LegacyOnlineWindow = TimeSpan.FromMinutes(5);
public AdminClientDirectoryService(
IAdminClientDirectoryRepository repository,
IAgentRepo agentRepo,
IAccessDeviceRepo accessDeviceRepo,
IPairingCodeRepo pairingCodeRepo,
IUnitOfWork unitOfWork,
IAuthService authService)
{
_repository = repository;
_agentRepo = agentRepo;
_accessDeviceRepo = accessDeviceRepo;
_pairingCodeRepo = pairingCodeRepo;
_UoW = unitOfWork;
_authService = authService;
}
public async Task<PagedResult<ClientSummary>> ListAsync(ClientDirectoryQuery query)
{
var (items, total) = await _repository.ListAsync(query.Search, query.Page, query.PageSize);
var summaries = items.Select(c => new ClientSummary(
c.ClientId,
c.ClientName,
c.ClientBusinesses.FirstOrDefault()?.City,
c.PaymentTrack?.ToString(),
c.ClientBusinesses.Any(b => b.IsActive),
c.ClientBusinesses.OrderBy(b => b.createdAt).FirstOrDefault()?.createdAt ?? DateTime.MinValue
)).ToList();
return new PagedResult<ClientSummary>(summaries, total, query.Page, query.PageSize);
}
public async Task<object> GetByIdAsync(Guid id)
{
var client = await _repository.GetByIdAsync(id)
?? throw new NotFoundException("Client not found.");
return new
{
client.ClientId,
client.ClientName,
client.ClientPhone,
client.ClientEmail,
PaymentTrack = client.PaymentTrack?.ToString(),
Businesses = client.ClientBusinesses.Select(b => new
{
b.Bid,
b.BusinessName,
b.Address,
b.City,
b.IsActive
})
};
}
public async Task ResendWelcomeEmailAsync(Guid clientId, HttpContext httpContext)
{
var client = await _repository.GetByIdAsync(clientId)
?? throw new NotFoundException("Client not found.");
if (string.IsNullOrWhiteSpace(client.ClientEmail))
throw new BadRequestException("This client has no email on file to send the link to.");
var authResponse = await _authService.ForgotPassword(
new { identifier = client.ClientEmail, useResetLink = true, welcome = true },
httpContext);
if (authResponse.StatusCode != 200 || !authResponse.Success)
throw new BadRequestException(authResponse.Message ?? "Couldn't resend the welcome email.");
}
public async Task<PairingCodeResult> GeneratePairingCodeAsync(Guid clientId)
{
if (!await _repository.ClientExistsAsync(clientId))
throw new NotFoundException("Client not found.");
var pairingCode = new PairingCode
{
Id = Guid.NewGuid(),
Code = GenerateShortCode(),
FKClientId = clientId,
ExpiresAt = DateTime.UtcNow.AddMinutes(15),
IsUsed = false,
CreatedAt = DateTime.UtcNow
};
await _UoW.BeginAsync();
try
{
await _pairingCodeRepo.AddAsync(pairingCode);
await _UoW.CommitAsync();
}
catch
{
await _UoW.RollbackAsync();
throw;
}
return new PairingCodeResult(pairingCode.Code, pairingCode.ExpiresAt);
}
public async Task<AdminClientDevicesResult> GetDevicesAsync(Guid clientId)
{
if (!await _repository.ClientExistsAsync(clientId))
throw new NotFoundException("Client not found.");
var agents = await _agentRepo.GetByClientIdAsync(clientId);
var devices = await _accessDeviceRepo.GetDevicesByClientAsync(clientId);
var now = DateTime.UtcNow;
var activeAgentIds = agents
.Where(a => a.IsActive)
.Select(a => a.AgentId)
.ToHashSet();
var agentDtos = agents
.Select(agent => new AdminAgentDto(
agent.AgentId,
agent.Name,
agent.IsActive,
agent.LastSeenAt,
agent.IsActive && agent.LastSeenAt.HasValue &&
now - agent.LastSeenAt.Value <= HealthOnlineWindow,
agent.CreatedAt))
.ToList();
var deviceDtos = devices
.Select(device =>
{
var hasHealthReport = device.LastHealthReportedAt.HasValue;
var presenceTimestamp = hasHealthReport
? device.LastHealthReportedAt
: device.LastSeenAt;
var window = hasHealthReport
? HealthOnlineWindow
: LegacyOnlineWindow;
var isOnline =
device.IsActive &&
device.FKAgentId.HasValue &&
activeAgentIds.Contains(device.FKAgentId.Value) &&
presenceTimestamp.HasValue &&
now - presenceTimestamp.Value <= window &&
(!hasHealthReport || device.IsReachable);
return new AdminDeviceDto(
device.DeviceId,
device.DeviceName,
device.IsActive,
device.LastSeenAt,
isOnline,
isOnline ? presenceTimestamp!.Value.Add(window) : null,
device.FKAgentId.HasValue,
device.CreatedAt);
})
.ToList();
return new AdminClientDevicesResult(agentDtos, deviceDtos);
}
private static string GenerateShortCode(int length = 8)
{
const string alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
var bytes = RandomNumberGenerator.GetBytes(length);
var chars = new char[length];
for (int i = 0; i < length; i++)
chars[i] = alphabet[bytes[i] % alphabet.Length];
return new string(chars);
}
}
}