using FirebaseAdmin.Messaging; using Marco.Pms.DataAccess.Data; using Marco.Pms.Model.Dtos.Attendance; using Marco.Pms.Model.Entitlements; using Marco.Pms.Services.Service.ServiceInterfaces; using MarcoBMS.Services.Service; using Microsoft.EntityFrameworkCore; namespace Marco.Pms.Services.Service { public class FirebaseService : IFirebaseService { private readonly IDbContextFactory _dbContextFactory; private readonly IServiceScopeFactory _serviceScopeFactory; public FirebaseService(IDbContextFactory dbContextFactory, IServiceScopeFactory serviceScopeFactory) { _dbContextFactory = dbContextFactory ?? throw new ArgumentNullException(nameof(dbContextFactory)); _serviceScopeFactory = serviceScopeFactory ?? throw new ArgumentNullException(nameof(serviceScopeFactory)); } public async Task SendLoginMessageAsync(string name) { await using var _context = await _dbContextFactory.CreateDbContextAsync(); // List of device registration tokens to send the message to var registrationTokens = await _context.FCMTokenMappings.Select(ft => ft.FcmToken).ToListAsync(); var notificationFirebase = new Notification { Title = "Login Alert", Body = $"{name} has successfully logged in to the application" }; await SendMessageToMultipleDevicesAsync(registrationTokens, notificationFirebase); } public async Task SendAttendanceMessageAsync(Guid projectId, string Name, ATTENDANCE_MARK_TYPE markType, Guid tenantId) { await using var _context = await _dbContextFactory.CreateDbContextAsync(); var projectTask = Task.Run(async () => { await using var dbContext = await _dbContextFactory.CreateDbContextAsync(); return await dbContext.ProjectAllocations .Include(pa => pa.Project) .Where(pa => pa.ProjectId == projectId && pa.IsActive && pa.Project != null) .GroupBy(pa => pa.ProjectId) .Select(g => new { ProjectName = g.Select(pa => pa.Project!.Name).FirstOrDefault(), EmployeeIds = g.Select(pa => pa.EmployeeId).Distinct().ToList() }).FirstOrDefaultAsync(); }); var teamAttendanceRoleTask = Task.Run(async () => { await using var dbContext = await _dbContextFactory.CreateDbContextAsync(); return await dbContext.RolePermissionMappings .Where(rp => rp.FeaturePermissionId == PermissionsMaster.TeamAttendance) .Select(rp => rp.ApplicationRoleId).ToListAsync(); }); var manageProjectsRoleTask = Task.Run(async () => { await using var dbContext = await _dbContextFactory.CreateDbContextAsync(); return await dbContext.RolePermissionMappings .Where(rp => rp.FeaturePermissionId == PermissionsMaster.ManageProject) .Select(rp => rp.ApplicationRoleId).ToListAsync(); }); await Task.WhenAll(projectTask, teamAttendanceRoleTask, manageProjectsRoleTask); var teamAttendanceRoleIds = teamAttendanceRoleTask.Result; var manageProjectsRoleIds = manageProjectsRoleTask.Result; var project = projectTask.Result; List projectAssignedEmployeeIds = project?.EmployeeIds ?? new List(); var employeeIds = await _context.EmployeeRoleMappings .Where(er => (projectAssignedEmployeeIds.Contains(er.EmployeeId) || manageProjectsRoleIds.Contains(er.RoleId)) && teamAttendanceRoleIds.Contains(er.RoleId)) .Select(er => er.EmployeeId) .ToListAsync(); Notification notificationFirebase; switch (markType) { case ATTENDANCE_MARK_TYPE.CHECK_IN: notificationFirebase = new Notification { Title = "Attendance Update", Body = $" {Name} has checked in for project {project?.ProjectName ?? ""}." }; break; case ATTENDANCE_MARK_TYPE.CHECK_OUT: notificationFirebase = new Notification { Title = "Attendance Update", Body = $" {Name} has checked out for project {project?.ProjectName ?? ""}." }; break; case ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE: notificationFirebase = new Notification { Title = "Regularization Request", Body = $" {Name} has submitted a regularization request for project {project?.ProjectName ?? ""}." }; break; case ATTENDANCE_MARK_TYPE.REGULARIZE: notificationFirebase = new Notification { Title = " Regularization Approved", Body = $" {Name}'s regularization request for project {project?.ProjectName ?? ""} has been accepted." }; break; case ATTENDANCE_MARK_TYPE.REGULARIZE_REJECT: notificationFirebase = new Notification { Title = "Regularization Denied", Body = $" {Name}'s regularization request for project {project?.ProjectName ?? ""} has been rejected." }; break; default: notificationFirebase = new Notification { Title = "Attendance Update", Body = $" {Name} has update his/her attendance for project {project?.ProjectName ?? ""}." }; break; } // List of device registration tokens to send the message to var registrationTokens = await _context.FCMTokenMappings .Where(ft => employeeIds.Contains(ft.EmployeeId) && ft.TenantId == tenantId) .Select(ft => ft.FcmToken).ToListAsync(); var data = new Dictionary() { { "Keyword", "Attendance" }, { "ProjectId", projectId.ToString() }, { "Action", markType.ToString() } }; await SendMessageToMultipleDevicesWithDataAsync(registrationTokens, notificationFirebase, data); } public async Task SendMessageToMultipleDevicesWithDataAsync(List registrationTokens, Notification notificationFirebase, Dictionary data) { using var scope = _serviceScopeFactory.CreateScope(); var _logger = scope.ServiceProvider.GetRequiredService(); try { var message = new MulticastMessage() { Tokens = registrationTokens, Data = data, Notification = notificationFirebase }; // Send the multicast message var response = await FirebaseMessaging.DefaultInstance.SendEachForMulticastAsync(message); _logger.LogInfo("{SuccessCount} messages were sent successfully.", response.SuccessCount); if (response.FailureCount > 0) { var failedTokens = new List(); for (int i = 0; i < response.Responses.Count; i++) { if (!response.Responses[i].IsSuccess) { failedTokens.Add(registrationTokens[i]); } } _logger.LogInfo("List of tokens that caused failures: " + string.Join(", ", failedTokens)); } } catch (FirebaseMessagingException ex) { // Log the specific Firebase error. _logger.LogError(ex, "Error sending push notification"); // Check for specific error codes that indicate an invalid or unregistered token. if (ex.MessagingErrorCode == MessagingErrorCode.Unregistered || ex.MessagingErrorCode == MessagingErrorCode.InvalidArgument) { _logger.LogWarning("FCM token is invalid and should be deleted from the database"); // TODO: Implement the logic here to remove the invalid token from your database. // Example: await YourTokenService.DeleteTokenAsync(loginDto.DeviceToken); } } catch (Exception ex) { _logger.LogError(ex, "Exception Occured while sending notification to firebase"); } } public async Task SendMessageToMultipleDevicesAsync(List registrationTokens, Notification notificationFirebase) { using var scope = _serviceScopeFactory.CreateScope(); var _logger = scope.ServiceProvider.GetRequiredService(); try { var message = new MulticastMessage() { Tokens = registrationTokens, Notification = notificationFirebase }; // Send the multicast message var response = await FirebaseMessaging.DefaultInstance.SendEachForMulticastAsync(message); _logger.LogInfo("{SuccessCount} messages were sent successfully.", response.SuccessCount); if (response.FailureCount > 0) { var failedTokens = new List(); for (int i = 0; i < response.Responses.Count; i++) { if (!response.Responses[i].IsSuccess) { failedTokens.Add(registrationTokens[i]); } } _logger.LogInfo("List of tokens that caused failures: " + string.Join(", ", failedTokens)); } } catch (FirebaseMessagingException ex) { // Log the specific Firebase error. _logger.LogError(ex, "Error sending push notification"); // Check for specific error codes that indicate an invalid or unregistered token. if (ex.MessagingErrorCode == MessagingErrorCode.Unregistered || ex.MessagingErrorCode == MessagingErrorCode.InvalidArgument) { _logger.LogWarning("FCM token is invalid and should be deleted from the database"); // TODO: Implement the logic here to remove the invalid token from your database. // Example: await YourTokenService.DeleteTokenAsync(loginDto.DeviceToken); } } } } }