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

153 lines
5.6 KiB
C#

using System.Net.Http.Json;
using System.Text.Json;
using Fitness_core_BE.Infra.UoW;
using Fitness_core_BE.Interfaces;
using Fitness_core_BE.Interfaces.Services;
using Fitness_core_BE.Models;
namespace Fitness_core_BE.Services
{
// Sends real device pushes via Expo's push API and, independently, persists a
// CustomerNotification row so the mobile Notification tab always has a history even if the
// push itself never reached the device (permission denied, no token registered, etc.).
public class PushNotificationService : IPushNotificationService
{
private const string ExpoPushUrl = "https://exp.host/--/api/v2/push/send";
private const int ExpoBatchSize = 100;
private readonly HttpClient _httpClient;
private readonly ICustomerNotificationRepository _repository;
private readonly IUnitOfWork _uow;
public PushNotificationService(IHttpClientFactory httpClientFactory, ICustomerNotificationRepository repository, IUnitOfWork uow)
{
_httpClient = httpClientFactory.CreateClient();
_repository = repository;
_uow = uow;
}
public async Task NotifyCustomerAsync(
Guid customerId,
string title,
string body,
string notificationType,
string? routeName = null,
object? routeParams = null)
{
var notification = BuildNotification(customerId, title, body, notificationType, routeName, routeParams);
await _uow.BeginAsync();
try
{
await _repository.CreateAsync(notification);
await _uow.CommitAsync();
}
catch
{
await _uow.RollbackAsync();
throw;
}
var pushToken = await _repository.GetCustomerPushTokenAsync(customerId);
if (IsExpoPushToken(pushToken))
{
await SendExpoPushBatchAsync(new[] { pushToken! }, title, body, notificationType, routeName, routeParams);
}
}
public async Task NotifyCustomersAsync(
IEnumerable<Guid> customerIds,
string title,
string body,
string notificationType,
string? routeName = null,
object? routeParams = null)
{
var ids = customerIds.Distinct().ToList();
if (ids.Count == 0) return;
var notifications = ids
.Select(id => BuildNotification(id, title, body, notificationType, routeName, routeParams))
.ToList();
await _uow.BeginAsync();
try
{
await _repository.CreateManyAsync(notifications);
await _uow.CommitAsync();
}
catch
{
await _uow.RollbackAsync();
throw;
}
var tokens = await _repository.GetCustomerPushTokensAsync(ids);
var validTokens = tokens.Where(IsExpoPushToken).ToList();
for (var offset = 0; offset < validTokens.Count; offset += ExpoBatchSize)
{
var chunk = validTokens.Skip(offset).Take(ExpoBatchSize).ToList();
await SendExpoPushBatchAsync(chunk, title, body, notificationType, routeName, routeParams);
}
}
private static CustomerNotification BuildNotification(
Guid customerId, string title, string body, string notificationType, string? routeName, object? routeParams)
{
return new CustomerNotification
{
CustomerNotificationId = Guid.NewGuid(),
FKCustomerId = customerId,
Title = title,
Body = body,
NotificationType = notificationType,
RouteName = routeName,
RouteParamsJson = routeParams != null ? JsonSerializer.Serialize(routeParams) : null,
IsRead = false,
CreatedAt = DateTime.UtcNow
};
}
private static bool IsExpoPushToken(string? token) =>
!string.IsNullOrWhiteSpace(token) && token.StartsWith("ExponentPushToken", StringComparison.OrdinalIgnoreCase);
private async Task SendExpoPushBatchAsync(
IReadOnlyCollection<string> tokens, string title, string body, string notificationType, string? routeName, object? routeParams)
{
if (tokens.Count == 0) return;
var messages = tokens.Select(token => new
{
to = token,
title,
body,
sound = "default",
data = new
{
notificationType = routeName != null ? "navigate" : "normal",
routeName,
routeParams,
notificationKind = notificationType
}
});
try
{
var response = await _httpClient.PostAsJsonAsync(ExpoPushUrl, messages);
if (!response.IsSuccessStatusCode)
{
var responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Expo push send failed ({(int)response.StatusCode}): {responseBody}");
}
}
catch (Exception ex)
{
// Push delivery is best-effort — the notification is already persisted above,
// so a network hiccup here must not fail the caller's larger operation.
Console.WriteLine($"Expo push send threw: {ex.Message}");
}
}
}
}