From 8814dc59d92969af1fe3c6f62d9877aa25206aa7 Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Wed, 11 Jun 2025 10:21:39 +0530 Subject: [PATCH 1/3] Add basic implementation and add in record attendance API for testing --- .../Controllers/AttendanceController.cs | 7 +++- .../Controllers/MasterController.cs | 10 +++--- Marco.Pms.Services/Hub/MarcoHub.cs | 35 +++++++++++++++++++ Marco.Pms.Services/Marco.Pms.Services.csproj | 1 + Marco.Pms.Services/Program.cs | 24 +++++++++++-- .../appsettings.Development.json | 2 +- 6 files changed, 70 insertions(+), 9 deletions(-) create mode 100644 Marco.Pms.Services/Hub/MarcoHub.cs diff --git a/Marco.Pms.Services/Controllers/AttendanceController.cs b/Marco.Pms.Services/Controllers/AttendanceController.cs index 5c7104d..0fbc6b5 100644 --- a/Marco.Pms.Services/Controllers/AttendanceController.cs +++ b/Marco.Pms.Services/Controllers/AttendanceController.cs @@ -7,11 +7,13 @@ using Marco.Pms.Model.Mapper; using Marco.Pms.Model.Projects; using Marco.Pms.Model.Utilities; using Marco.Pms.Model.ViewModels.AttendanceVM; +using Marco.Pms.Services.Hubs; using Marco.Pms.Services.Service; using MarcoBMS.Services.Helpers; using MarcoBMS.Services.Service; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.SignalR; using Microsoft.CodeAnalysis; using Microsoft.EntityFrameworkCore; using Document = Marco.Pms.Model.DocumentManager.Document; @@ -30,10 +32,11 @@ namespace MarcoBMS.Services.Controllers private readonly S3UploadService _s3Service; private readonly PermissionServices _permission; private readonly ILoggingService _logger; + private readonly IHubContext _signalR; public AttendanceController( - ApplicationDbContext context, EmployeeHelper employeeHelper, ProjectsHelper projectsHelper, UserHelper userHelper, S3UploadService s3Service, ILoggingService logger, PermissionServices permission) + ApplicationDbContext context, EmployeeHelper employeeHelper, ProjectsHelper projectsHelper, UserHelper userHelper, S3UploadService s3Service, ILoggingService logger, PermissionServices permission, IHubContext signalR) { _context = context; _employeeHelper = employeeHelper; @@ -42,6 +45,7 @@ namespace MarcoBMS.Services.Controllers _s3Service = s3Service; _logger = logger; _permission = permission; + _signalR = signalR; } private Guid GetTenantId() @@ -558,6 +562,7 @@ namespace MarcoBMS.Services.Controllers Activity = attendance.Activity, JobRoleName = employee.JobRole.Name }; + await _signalR.Clients.All.SendAsync("Attendance", new { LoggedInUserId = currentEmployee.Id, ProjectId = recordAttendanceDot.ProjectID, Response = vm }); _logger.LogInfo("Attendance for employee {FirstName} {LastName} has been marked", employee.FirstName ?? string.Empty, employee.LastName ?? string.Empty); return Ok(ApiResponse.SuccessResponse(vm, "Attendance marked successfully.", 200)); } diff --git a/Marco.Pms.Services/Controllers/MasterController.cs b/Marco.Pms.Services/Controllers/MasterController.cs index 3512081..7835e8d 100644 --- a/Marco.Pms.Services/Controllers/MasterController.cs +++ b/Marco.Pms.Services/Controllers/MasterController.cs @@ -749,11 +749,11 @@ namespace Marco.Pms.Services.Controllers return Ok(response); } - [HttpGet("contact-tag/{id}")] - public async Task GetContactTagMaster(Guid id) - { - return Ok(); - } + //[HttpGet("contact-tag/{id}")] + //public async Task GetContactTagMaster(Guid id) + //{ + // return Ok(); + //} [HttpPost("contact-tag")] public async Task CreateContactTagMaster([FromBody] CreateContactTagDto contactTagDto) diff --git a/Marco.Pms.Services/Hub/MarcoHub.cs b/Marco.Pms.Services/Hub/MarcoHub.cs new file mode 100644 index 0000000..bf6fc38 --- /dev/null +++ b/Marco.Pms.Services/Hub/MarcoHub.cs @@ -0,0 +1,35 @@ +using MarcoBMS.Services.Service; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.SignalR; + +namespace Marco.Pms.Services.Hubs +{ + [Authorize] + public class MarcoHub : Hub + { + private readonly ILoggingService _logger; + public MarcoHub(ILoggingService logger) + { + _logger = logger; + } + public async Task SendMessage(string user, string message) + { + _logger.LogInfo($"User: {user} Message : {message}"); + await Clients.All.SendAsync("ReceiveMessage", user, message); + } + public override async Task OnConnectedAsync() + { + await base.OnConnectedAsync(); + _logger.LogInfo($"Connected successfully"); + await Clients.All.SendAsync("Connected successfully"); + } + + // Optional: override OnDisconnectedAsync + public override async Task OnDisconnectedAsync(Exception? exception) + { + await base.OnDisconnectedAsync(exception); + _logger.LogInfo($"DIsonnected successfully"); + await Clients.All.SendAsync("Disonnected successfully"); + } + } +} diff --git a/Marco.Pms.Services/Marco.Pms.Services.csproj b/Marco.Pms.Services/Marco.Pms.Services.csproj index 6482198..7bef32f 100644 --- a/Marco.Pms.Services/Marco.Pms.Services.csproj +++ b/Marco.Pms.Services/Marco.Pms.Services.csproj @@ -17,6 +17,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Marco.Pms.Services/Program.cs b/Marco.Pms.Services/Program.cs index e57fa15..17eb5c7 100644 --- a/Marco.Pms.Services/Program.cs +++ b/Marco.Pms.Services/Program.cs @@ -4,6 +4,7 @@ using Marco.Pms.Model.Authentication; using Marco.Pms.Model.Entitlements; using Marco.Pms.Model.Utilities; using Marco.Pms.Services.Helpers; +using Marco.Pms.Services.Hubs; using Marco.Pms.Services.Service; using MarcoBMS.Services.Helpers; using MarcoBMS.Services.Middleware; @@ -59,7 +60,8 @@ builder.Services.AddCors(options => { policy.AllowAnyOrigin() .AllowAnyMethod() - .AllowAnyHeader(); + .AllowAnyHeader() + .WithExposedHeaders("Authorization"); }); }); @@ -161,10 +163,28 @@ if (jwtSettings != null && jwtSettings.Key != null) ValidAudience = jwtSettings.Audience, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.Key)) }; + + options.Events = new JwtBearerEvents + { + OnMessageReceived = context => + { + var accessToken = context.Request.Query["access_token"]; + var path = context.HttpContext.Request.Path; + + // Match your hub route here + if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/hubs/marco")) + { + context.Token = accessToken; + } + + return Task.CompletedTask; + } + }; }); builder.Services.AddSingleton(jwtSettings); } +builder.Services.AddSignalR(); builder.WebHost.ConfigureKestrel(options => { options.AddServerHeader = false; // Disable the "Server" header @@ -207,7 +227,7 @@ app.UseHttpsRedirection(); app.UseAuthorization(); - +app.MapHub("/hubs/marco"); app.MapControllers(); app.Run(); diff --git a/Marco.Pms.Services/appsettings.Development.json b/Marco.Pms.Services/appsettings.Development.json index 2cbcbcf..1565018 100644 --- a/Marco.Pms.Services/appsettings.Development.json +++ b/Marco.Pms.Services/appsettings.Development.json @@ -9,7 +9,7 @@ "Title": "Dev" }, "ConnectionStrings": { - "DefaultConnectionString": "Server=147.93.98.152;User ID=devuser;Password=AppUser@123$;Database=MarcoBMSGuid" + "DefaultConnectionString": "Server=147.93.98.152;User ID=devuser;Password=AppUser@123$;Database=MarcoBMS1" }, "SmtpSettings": { "SmtpServer": "smtp.gmail.com", From 712c5e2a0a764f1f9e0d65a7f59bc355bd89850e Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Thu, 12 Jun 2025 11:04:24 +0530 Subject: [PATCH 2/3] Implemented signalR in project infrastructure APIs --- .../Controllers/AttendanceController.cs | 8 ++- .../Controllers/ProjectController.cs | 63 +++++++++++++------ 2 files changed, 51 insertions(+), 20 deletions(-) diff --git a/Marco.Pms.Services/Controllers/AttendanceController.cs b/Marco.Pms.Services/Controllers/AttendanceController.cs index 0fbc6b5..e0493af 100644 --- a/Marco.Pms.Services/Controllers/AttendanceController.cs +++ b/Marco.Pms.Services/Controllers/AttendanceController.cs @@ -562,7 +562,13 @@ namespace MarcoBMS.Services.Controllers Activity = attendance.Activity, JobRoleName = employee.JobRole.Name }; - await _signalR.Clients.All.SendAsync("Attendance", new { LoggedInUserId = currentEmployee.Id, ProjectId = recordAttendanceDot.ProjectID, Response = vm }); + var sendActivity = 0; + if (recordAttendanceDot.Id == Guid.Empty) + { + sendActivity = 1; + } + var notification = new { LoggedInUserId = currentEmployee.Id, Keyword = "Attendance", Activity = sendActivity, ProjectId = attendance.ProjectID, Response = vm }; + await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification); _logger.LogInfo("Attendance for employee {FirstName} {LastName} has been marked", employee.FirstName ?? string.Empty, employee.LastName ?? string.Empty); return Ok(ApiResponse.SuccessResponse(vm, "Attendance marked successfully.", 200)); } diff --git a/Marco.Pms.Services/Controllers/ProjectController.cs b/Marco.Pms.Services/Controllers/ProjectController.cs index 43e7252..839a872 100644 --- a/Marco.Pms.Services/Controllers/ProjectController.cs +++ b/Marco.Pms.Services/Controllers/ProjectController.cs @@ -8,12 +8,13 @@ using Marco.Pms.Model.Projects; using Marco.Pms.Model.Utilities; using Marco.Pms.Model.ViewModels.Employee; using Marco.Pms.Model.ViewModels.Projects; +using Marco.Pms.Services.Hubs; using MarcoBMS.Services.Helpers; using MarcoBMS.Services.Service; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.SignalR; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; namespace MarcoBMS.Services.Controllers { @@ -27,15 +28,18 @@ namespace MarcoBMS.Services.Controllers private readonly ILoggingService _logger; private readonly RolesHelper _rolesHelper; private readonly ProjectsHelper _projectsHelper; + private readonly IHubContext _signalR; - public ProjectController(ApplicationDbContext context, UserHelper userHelper, ILoggingService logger, RolesHelper rolesHelper, ProjectsHelper projectHelper) + public ProjectController(ApplicationDbContext context, UserHelper userHelper, ILoggingService logger, RolesHelper rolesHelper, ProjectsHelper projectHelper, IHubContext signalR) { _context = context; _userHelper = userHelper; _logger = logger; _rolesHelper = rolesHelper; _projectsHelper = projectHelper; + _signalR = signalR; + } [HttpGet("list")] public async Task GetAll() @@ -261,6 +265,7 @@ namespace MarcoBMS.Services.Controllers [HttpPost] public async Task Create([FromBody] CreateProjectDto projectDto) { + var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); if (!ModelState.IsValid) { var errors = ModelState.Values @@ -277,6 +282,9 @@ namespace MarcoBMS.Services.Controllers _context.Projects.Add(project); await _context.SaveChangesAsync(); + var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Project", Response = project.ToProjectDto() }; + + await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification); return Ok(ApiResponse.SuccessResponse(project.ToProjectDto(), "Success.", 200)); } @@ -285,6 +293,7 @@ namespace MarcoBMS.Services.Controllers [Route("update/{id}")] public async Task Update([FromRoute] Guid id, [FromBody] UpdateProjectDto updateProjectDto) { + var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); if (!ModelState.IsValid) { var errors = ModelState.Values @@ -303,6 +312,10 @@ namespace MarcoBMS.Services.Controllers await _context.SaveChangesAsync(); + var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Project", Response = project.ToProjectDto() }; + + await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification); + return Ok(ApiResponse.SuccessResponse(project.ToProjectDto(), "Success.", 200)); } @@ -312,7 +325,6 @@ namespace MarcoBMS.Services.Controllers } } - //[HttpPost("assign-employee")] //public async Task AssignEmployee(int? allocationid, int employeeId, int projectId) //{ @@ -523,6 +535,7 @@ namespace MarcoBMS.Services.Controllers public async Task CreateProjectTask(List workItemDot) { Guid tenantId = GetTenantId(); + var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); List workItems = new List { }; string responseMessage = ""; if (workItemDot != null) @@ -554,6 +567,10 @@ namespace MarcoBMS.Services.Controllers } var activity = await _context.ActivityMasters.ToListAsync(); var category = await _context.WorkCategoryMasters.ToListAsync(); + + var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Infra", Response = workItems }; + + await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification); return Ok(ApiResponse.SuccessResponse(workItems, responseMessage, 200)); } @@ -565,6 +582,7 @@ namespace MarcoBMS.Services.Controllers public async Task DeleteProjectTask(Guid id) { Guid tenantId = _userHelper.GetTenantId(); + var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); WorkItem? task = await _context.WorkItems.AsNoTracking().FirstOrDefaultAsync(t => t.Id == id && t.TenantId == tenantId); if (task != null) { @@ -576,6 +594,9 @@ namespace MarcoBMS.Services.Controllers _context.WorkItems.Remove(task); await _context.SaveChangesAsync(); _logger.LogInfo("Task with ID {WorkItemId} has been successfully deleted.", task.Id); + + var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Infra", Response = id }; + await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification); } else { @@ -603,6 +624,8 @@ namespace MarcoBMS.Services.Controllers public async Task ManageProjectInfra(List infraDots) { Guid tenantId = GetTenantId(); + var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + var responseData = new InfraVM { }; string responseMessage = ""; if (infraDots != null) @@ -678,6 +701,9 @@ namespace MarcoBMS.Services.Controllers } } } + var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Infra", Response = responseData }; + + await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification); return Ok(ApiResponse.SuccessResponse(responseData, responseMessage, 200)); } return BadRequest(ApiResponse.ErrorResponse("Invalid details.", "Infra Details are not valid.", 400)); @@ -701,46 +727,44 @@ namespace MarcoBMS.Services.Controllers if (!projectList.Any()) { - return NotFound(ApiResponse.SuccessResponse(new List(), "No projects found.",200)); + return NotFound(ApiResponse.SuccessResponse(new List(), "No projects found.", 200)); } - List projectlist = await _context.Projects - .Where(p => projectList.Contains(p.Id)) - .ToListAsync(); + List projectlist = await _context.Projects + .Where(p => projectList.Contains(p.Id)) + .ToListAsync(); List projects = new List(); - foreach (var project in projectlist) { - + foreach (var project in projectlist) + { + projects.Add(project.ToProjectListVMFromProject()); } - + return Ok(ApiResponse.SuccessResponse(projects, "Success.", 200)); } - - - [HttpPost("assign-projects/{employeeId}")] public async Task AssigneProjectsToEmployee([FromBody] List projectAllocationDtos, [FromRoute] Guid employeeId) { - if(projectAllocationDtos != null && employeeId != Guid.Empty) + if (projectAllocationDtos != null && employeeId != Guid.Empty) { Guid TenentID = GetTenantId(); List? result = new List(); - foreach(var projectAllocationDto in projectAllocationDtos) + foreach (var projectAllocationDto in projectAllocationDtos) { try { - ProjectAllocation projectAllocation = projectAllocationDto.ToProjectAllocationFromProjectsAllocationDto(TenentID,employeeId); + ProjectAllocation projectAllocation = projectAllocationDto.ToProjectAllocationFromProjectsAllocationDto(TenentID, employeeId); ProjectAllocation? projectAllocationFromDb = await _context.ProjectAllocations.Where(c => c.EmployeeId == employeeId && c.ProjectId == projectAllocationDto.ProjectId && c.ReAllocationDate == null && c.TenantId == TenentID).SingleOrDefaultAsync(); - if(projectAllocationFromDb != null) + if (projectAllocationFromDb != null) { @@ -785,7 +809,8 @@ namespace MarcoBMS.Services.Controllers } - catch (Exception ex) { + catch (Exception ex) + { return Ok(ApiResponse.ErrorResponse(ex.Message, ex, 400)); } @@ -797,7 +822,7 @@ namespace MarcoBMS.Services.Controllers { return BadRequest(ApiResponse.ErrorResponse("Invalid details.", "All Field is required", 400)); } - + } From 169e7f6601f0999cfb51a3515003322236c1fe42 Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Thu, 12 Jun 2025 20:53:24 +0530 Subject: [PATCH 3/3] Changed the keyword for updating and creating project --- Marco.Pms.Services/Controllers/ProjectController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Marco.Pms.Services/Controllers/ProjectController.cs b/Marco.Pms.Services/Controllers/ProjectController.cs index 839a872..34a0384 100644 --- a/Marco.Pms.Services/Controllers/ProjectController.cs +++ b/Marco.Pms.Services/Controllers/ProjectController.cs @@ -282,7 +282,7 @@ namespace MarcoBMS.Services.Controllers _context.Projects.Add(project); await _context.SaveChangesAsync(); - var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Project", Response = project.ToProjectDto() }; + var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Create_Project", Response = project.ToProjectDto() }; await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification); @@ -312,7 +312,7 @@ namespace MarcoBMS.Services.Controllers await _context.SaveChangesAsync(); - var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Project", Response = project.ToProjectDto() }; + var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Update_Project", Response = project.ToProjectDto() }; await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);