Added the firebase services

This commit is contained in:
ashutosh.nehete 2025-08-13 11:52:44 +05:30
parent 47a3d6035c
commit 58b817be99
9 changed files with 4715 additions and 49 deletions

View File

@ -102,6 +102,8 @@ namespace Marco.Pms.DataAccess.Data
public DbSet<StatusPermissionMapping> StatusPermissionMapping { get; set; } public DbSet<StatusPermissionMapping> StatusPermissionMapping { get; set; }
public DbSet<ExpensesStatusMapping> ExpensesStatusMapping { get; set; } public DbSet<ExpensesStatusMapping> ExpensesStatusMapping { get; set; }
public DbSet<FCMTokenMapping> FCMTokenMappings { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,49 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Marco.Pms.DataAccess.Migrations
{
/// <inheritdoc />
public partial class Added_FCMTokenMApping_Table : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "FCMTokenMappings",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
EmployeeId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
FcmToken = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
TenantId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci")
},
constraints: table =>
{
table.PrimaryKey("PK_FCMTokenMappings", x => x.Id);
table.ForeignKey(
name: "FK_FCMTokenMappings_Tenants_TenantId",
column: x => x.TenantId,
principalTable: "Tenants",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_FCMTokenMappings_TenantId",
table: "FCMTokenMappings",
column: "TenantId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "FCMTokenMappings");
}
}
}

View File

@ -3058,6 +3058,29 @@ namespace Marco.Pms.DataAccess.Migrations
b.ToTable("JobRoles"); b.ToTable("JobRoles");
}); });
modelBuilder.Entity("Marco.Pms.Model.Utilities.FCMTokenMapping", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<Guid>("EmployeeId")
.HasColumnType("char(36)");
b.Property<string>("FcmToken")
.IsRequired()
.HasColumnType("longtext");
b.Property<Guid>("TenantId")
.HasColumnType("char(36)");
b.HasKey("Id");
b.HasIndex("TenantId");
b.ToTable("FCMTokenMappings");
});
modelBuilder.Entity("Marco.Pms.Model.Utilities.Inquiries", b => modelBuilder.Entity("Marco.Pms.Model.Utilities.Inquiries", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
@ -4368,6 +4391,17 @@ namespace Marco.Pms.DataAccess.Migrations
b.Navigation("Tenant"); b.Navigation("Tenant");
}); });
modelBuilder.Entity("Marco.Pms.Model.Utilities.FCMTokenMapping", b =>
{
b.HasOne("Marco.Pms.Model.Entitlements.Tenant", "Tenant")
.WithMany()
.HasForeignKey("TenantId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Tenant");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b => modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{ {
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)

View File

@ -2,8 +2,8 @@
{ {
public class FCMTokenMapping : TenantRelation public class FCMTokenMapping : TenantRelation
{ {
public Guid Id { get; set; }
public Guid EmployeeId { get; set; } public Guid EmployeeId { get; set; }
public string FCcmToken { get; set; } = string.Empty;
public string FcmToken { get; set; } = string.Empty; public string FcmToken { get; set; } = string.Empty;
} }
} }

View File

@ -1,11 +1,11 @@
using FirebaseAdmin.Messaging; using Marco.Pms.DataAccess.Data;
using Marco.Pms.DataAccess.Data;
using Marco.Pms.Model.Authentication; using Marco.Pms.Model.Authentication;
using Marco.Pms.Model.Dtos.Authentication; using Marco.Pms.Model.Dtos.Authentication;
using Marco.Pms.Model.Dtos.Util; using Marco.Pms.Model.Dtos.Util;
using Marco.Pms.Model.Employees; using Marco.Pms.Model.Employees;
using Marco.Pms.Model.Entitlements; using Marco.Pms.Model.Entitlements;
using Marco.Pms.Model.Utilities; using Marco.Pms.Model.Utilities;
using Marco.Pms.Services.Service.ServiceInterfaces;
using MarcoBMS.Services.Helpers; using MarcoBMS.Services.Helpers;
using MarcoBMS.Services.Service; using MarcoBMS.Services.Service;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@ -32,9 +32,10 @@ namespace MarcoBMS.Services.Controllers
private readonly IConfiguration _configuration; private readonly IConfiguration _configuration;
private readonly EmployeeHelper _employeeHelper; private readonly EmployeeHelper _employeeHelper;
private readonly ILoggingService _logger; private readonly ILoggingService _logger;
private readonly IFirebaseService _firebase;
//string tenentId = "1"; //string tenentId = "1";
public AuthController(UserManager<ApplicationUser> userManager, ApplicationDbContext context, JwtSettings jwtSettings, RefreshTokenService refreshTokenService, public AuthController(UserManager<ApplicationUser> userManager, ApplicationDbContext context, JwtSettings jwtSettings, RefreshTokenService refreshTokenService,
IEmailSender emailSender, IConfiguration configuration, EmployeeHelper employeeHelper, UserHelper userHelper, ILoggingService logger) IEmailSender emailSender, IConfiguration configuration, EmployeeHelper employeeHelper, UserHelper userHelper, ILoggingService logger, IFirebaseService firebase)
{ {
_userManager = userManager; _userManager = userManager;
_jwtSettings = jwtSettings; _jwtSettings = jwtSettings;
@ -45,6 +46,7 @@ namespace MarcoBMS.Services.Controllers
_context = context; _context = context;
_userHelper = userHelper; _userHelper = userHelper;
_logger = logger; _logger = logger;
_firebase = firebase;
} }
[HttpPost("login")] [HttpPost("login")]
@ -222,42 +224,40 @@ namespace MarcoBMS.Services.Controllers
// Return a successful response with the generated tokens. // Return a successful response with the generated tokens.
_logger.LogInfo("User {Username} logged in successfully.", user.UserName); _logger.LogInfo("User {Username} logged in successfully.", user.UserName);
var exsistingFCMMapping = await _context.FCMTokenMappings.FirstOrDefaultAsync(ft => ft.EmployeeId == emp.Id);
if (exsistingFCMMapping == null)
{
var fcmTokenMapping = new FCMTokenMapping
{
EmployeeId = emp.Id,
FcmToken = loginDto.FcmToken,
TenantId = emp.TenantId
};
_context.FCMTokenMappings.Add(fcmTokenMapping);
_logger.LogInfo("New FCM Token registering for employee {EmployeeId}", emp.Id);
}
else
{
exsistingFCMMapping.FcmToken = loginDto.FcmToken;
_logger.LogInfo("Updating FCM Token for employee {EmployeeId}", emp.Id);
}
try
{
await _context.SaveChangesAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "Exception occured while saving FCM Token for employee {EmployeeId}", emp.Id);
return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal Error", ex.Message, 500));
}
_ = Task.Run(async () => _ = Task.Run(async () =>
{ {
// --- Push Notification Section --- // --- Push Notification Section ---
// This section attempts to send a test push notification to the user's device. // This section attempts to send a test push notification to the user's device.
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens. // It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
var message = new Message() await _firebase.SendMessageToMultipleDevicesAsync();
{
Token = loginDto.FcmToken,
Notification = new Notification
{
Title = "Hello from AuthController",
Body = "This is a test with not increased response time message"
}
};
try
{
// Attempt to send the message via Firebase.
string response = await FirebaseMessaging.DefaultInstance.SendAsync(message);
_logger.LogInfo("Successfully sent test push notification. MessageId: {MessageId}", response);
}
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: {Token}", loginDto.FcmToken ?? string.Empty);
// TODO: Implement the logic here to remove the invalid token from your database.
// Example: await YourTokenService.DeleteTokenAsync(loginDto.DeviceToken);
}
}
}); });
return Ok(ApiResponse<object>.SuccessResponse(responseData, "User logged in successfully.", 200)); return Ok(ApiResponse<object>.SuccessResponse(responseData, "User logged in successfully.", 200));
} }
@ -899,22 +899,34 @@ namespace MarcoBMS.Services.Controllers
{ {
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var tenantId = _userHelper.GetTenantId(); var tenantId = _userHelper.GetTenantId();
var exsistingFCMMapping = await _context.FCMTokenMappings.FirstOrDefaultAsync(ft => ft.EmployeeId == loggedInEmployee.Id);
if (exsistingFCMMapping == null)
{
var fcmTokenMapping = new FCMTokenMapping var fcmTokenMapping = new FCMTokenMapping
{ {
EmployeeId = loggedInEmployee.Id, EmployeeId = loggedInEmployee.Id,
FcmToken = model.FcmToken, FcmToken = model.FcmToken,
TenantId = tenantId TenantId = tenantId
}; };
//_context.FCMTokenMappings.Add(fcmTokenMapping); _context.FCMTokenMappings.Add(fcmTokenMapping);
//try _logger.LogInfo("New FCM Token registering for employee {EmployeeId}", loggedInEmployee.Id);
//{ }
// await _context.SaveChangesAsync(); else
//} {
//catch (Exception ex) exsistingFCMMapping.FcmToken = model.FcmToken;
//{ _logger.LogInfo("Updating FCM Token for employee {EmployeeId}", loggedInEmployee.Id);
// _logger.LogError(ex, "Exception occured while saving FCM Token for employee {EmployeeId}", loggedInEmployee.Id); }
//} try
return Ok(ApiResponse<object>.SuccessResponse(fcmTokenMapping)); {
await _context.SaveChangesAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "Exception occured while saving FCM Token for employee {EmployeeId}", loggedInEmployee.Id);
return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal Error", ex.Message, 500));
}
return Ok(ApiResponse<object>.SuccessResponse(new { }, "FCM Token registered Successfuly", 200));
} }
private static string ComputeSha256Hash(string rawData) private static string ComputeSha256Hash(string rawData)
{ {

View File

@ -178,6 +178,7 @@ builder.Services.AddScoped<ISignalRService, SignalRService>();
builder.Services.AddScoped<IProjectServices, ProjectServices>(); builder.Services.AddScoped<IProjectServices, ProjectServices>();
builder.Services.AddScoped<IExpensesService, ExpensesService>(); builder.Services.AddScoped<IExpensesService, ExpensesService>();
builder.Services.AddScoped<IMasterService, MasterService>(); builder.Services.AddScoped<IMasterService, MasterService>();
builder.Services.AddScoped<IFirebaseService, FirebaseService>();
#endregion #endregion
#region Helpers #region Helpers

View File

@ -0,0 +1,90 @@
using FirebaseAdmin.Messaging;
using Marco.Pms.DataAccess.Data;
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<ApplicationDbContext> _dbContextFactory;
private readonly ILoggingService _logger;
public FirebaseService(IDbContextFactory<ApplicationDbContext> dbContextFactory,
ILoggingService logger)
{
_dbContextFactory = dbContextFactory ?? throw new ArgumentNullException(nameof(dbContextFactory));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task SendDemoMessages(Notification notificationFirebase)
{
string deviceToken = "";
var message = new Message()
{
Token = deviceToken,
Notification = notificationFirebase
};
string response = await FirebaseMessaging.DefaultInstance.SendAsync(message);
}
public async Task SendMessageToMultipleDevicesAsync()
{
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 registrationTokens = new List<string>
//{
// "YOUR_REGISTRATION_TOKEN_1",
// "YOUR_REGISTRATION_TOKEN_2",
// // add up to 500 tokens
//};
var message = new MulticastMessage()
{
Tokens = registrationTokens,
Notification = new Notification
{
Title = "Testing from API",
Body = "This messages comes from FireBase Services"
}
};
try
{
// 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<string>();
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);
}
}
}
}
}

View File

@ -0,0 +1,7 @@
namespace Marco.Pms.Services.Service.ServiceInterfaces
{
public interface IFirebaseService
{
Task SendMessageToMultipleDevicesAsync();
}
}