From 4370d5a350753d2e9852412195a9c30eab6dc0fc Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Wed, 23 Jul 2025 16:24:59 +0530 Subject: [PATCH] Adsing file to delete from S3 in mongoDB while update expenes --- Marco.Pms.Helpers/CacheHelper/ExpenseCache.cs | 10 +++ .../UtilityMongoDBHelper.cs} | 43 +++++++++++- .../MongoDBModels/Utility/S3DeletionObject.cs | 15 ++++ Marco.Pms.Model/Utilities/FileUploadModel.cs | 2 + .../Helpers/CacheUpdateHelper.cs | 45 ++++++++++-- Marco.Pms.Services/Program.cs | 3 +- Marco.Pms.Services/Service/ExpensesService.cs | 69 +++++++++++++++++-- Marco.Pms.Services/Service/S3UploadService.cs | 3 +- 8 files changed, 176 insertions(+), 14 deletions(-) rename Marco.Pms.Helpers/{UpdateLogHelper.cs => Utility/UtilityMongoDBHelper.cs} (63%) create mode 100644 Marco.Pms.Model/MongoDBModels/Utility/S3DeletionObject.cs diff --git a/Marco.Pms.Helpers/CacheHelper/ExpenseCache.cs b/Marco.Pms.Helpers/CacheHelper/ExpenseCache.cs index 02263e2..5d29088 100644 --- a/Marco.Pms.Helpers/CacheHelper/ExpenseCache.cs +++ b/Marco.Pms.Helpers/CacheHelper/ExpenseCache.cs @@ -102,6 +102,16 @@ namespace Marco.Pms.Helpers.CacheHelper return expense; } + + public async Task DeleteExpenseFromCacheAsync(Guid id, Guid tenantId) + { + var filter = Builders.Filter.And( + Builders.Filter.Eq(e => e.Id, id.ToString()), + Builders.Filter.Eq(e => e.TenantId, tenantId.ToString()) + ); + var result = await _collection.DeleteOneAsync(filter); + return result.DeletedCount > 0; + } private async Task InitializeCollectionAsync() { var indexKeys = Builders.IndexKeys.Ascending(x => x.ExpireAt); diff --git a/Marco.Pms.Helpers/UpdateLogHelper.cs b/Marco.Pms.Helpers/Utility/UtilityMongoDBHelper.cs similarity index 63% rename from Marco.Pms.Helpers/UpdateLogHelper.cs rename to Marco.Pms.Helpers/Utility/UtilityMongoDBHelper.cs index 5c7595f..7159850 100644 --- a/Marco.Pms.Helpers/UpdateLogHelper.cs +++ b/Marco.Pms.Helpers/Utility/UtilityMongoDBHelper.cs @@ -1,21 +1,28 @@ using Marco.Pms.Model.MongoDBModels.Utility; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; using MongoDB.Bson; using MongoDB.Driver; using System.Collections; -namespace Marco.Pms.Helpers +namespace Marco.Pms.Helpers.Utility { - public class UpdateLogHelper + public class UtilityMongoDBHelper { private readonly IMongoDatabase _mongoDatabase; - public UpdateLogHelper(IConfiguration configuration) + private readonly IConfiguration _configuration; + private readonly ILogger _logger; + public UtilityMongoDBHelper(IConfiguration configuration, ILogger logger) { + _configuration = configuration; + _logger = logger; var connectionString = configuration["MongoDB:ModificationConnectionString"]; var mongoUrl = new MongoUrl(connectionString); var client = new MongoClient(mongoUrl); // Your MongoDB connection string _mongoDatabase = client.GetDatabase(mongoUrl.DatabaseName); // Your MongoDB Database name } + + #region =================================================================== Update Log Helper Functions =================================================================== public async Task PushToUpdateLogsAsync(UpdateLogsObject oldObject, string collectionName) { var collection = _mongoDatabase.GetCollection(collectionName); @@ -87,5 +94,35 @@ namespace Marco.Pms.Helpers return bson; } + #endregion + + #region =================================================================== S3 deletion Helper Functions =================================================================== + + public async Task PushToS3DeletionAsync(List deletionObject) + { + var bucketName = _configuration["AWS:BucketName"]; + if (bucketName != null) + { + deletionObject = deletionObject.Select(d => new S3DeletionObject + { + BucketName = bucketName, + Key = d.Key, + Deleted = false + }).ToList(); + } + _logger.LogInformation("Delection object for bucket {BucketName} added to mongoDB", bucketName); + try + { + var collection = _mongoDatabase.GetCollection("S3Delection"); + await collection.InsertManyAsync(deletionObject); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error occured while saving delection object for S3 to MogoDB"); + } + _logger.LogInformation("Delection Objects added to MongoDB Successfully"); + } + + #endregion } } diff --git a/Marco.Pms.Model/MongoDBModels/Utility/S3DeletionObject.cs b/Marco.Pms.Model/MongoDBModels/Utility/S3DeletionObject.cs new file mode 100644 index 0000000..bb957de --- /dev/null +++ b/Marco.Pms.Model/MongoDBModels/Utility/S3DeletionObject.cs @@ -0,0 +1,15 @@ +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; + +namespace Marco.Pms.Model.MongoDBModels.Utility +{ + public class S3DeletionObject + { + [BsonId] + [BsonRepresentation(BsonType.String)] + public Guid Id { get; set; } = Guid.NewGuid(); + public string BucketName { get; set; } = string.Empty; + public string Key { get; set; } = string.Empty; + public bool Deleted { get; set; } = false; + } +} diff --git a/Marco.Pms.Model/Utilities/FileUploadModel.cs b/Marco.Pms.Model/Utilities/FileUploadModel.cs index 93ecb2c..98a6a26 100644 --- a/Marco.Pms.Model/Utilities/FileUploadModel.cs +++ b/Marco.Pms.Model/Utilities/FileUploadModel.cs @@ -2,10 +2,12 @@ { public class FileUploadModel { + public Guid? DocumentId { get; set; } public string? FileName { get; set; } // Name of the file (e.g., "image1.png") public string? Base64Data { get; set; } // Base64-encoded string of the file public string? ContentType { get; set; } // MIME type (e.g., "image/png", "application/pdf") public long FileSize { get; set; } // File size in bytes public string? Description { get; set; } // Optional: Description or purpose of the file + public bool IsActive { get; set; } = true; } } diff --git a/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs b/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs index d7466c6..9acf08f 100644 --- a/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs +++ b/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs @@ -1026,12 +1026,49 @@ namespace Marco.Pms.Services.Helpers public async Task GetExpenseDetailsById(Guid id, Guid tenantId) { - var response = await _expenseCache.GetExpenseDetailsByIdAsync(id, tenantId); - if (response == null || response.Id == string.Empty) + try { - return null; + var response = await _expenseCache.GetExpenseDetailsByIdAsync(id, tenantId); + if (response != null && response.Id != string.Empty) + { + return response; + } } - return response; + catch (Exception ex) + { + _logger.LogError(ex, "Error occured while fetching expense details from cache"); + } + return null; + } + + public async Task ReplaceExpenseAsync(Expenses expense) + { + bool response = false; + try + { + response = await _expenseCache.DeleteExpenseFromCacheAsync(expense.Id, expense.TenantId); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error occured while deleting expense from cache"); + } + if (response) + { + await AddExpenseByObjectAsync(expense); + } + + } + public async Task DeleteExpenseAsync(Guid id, Guid tenantId) + { + try + { + var response = await _expenseCache.DeleteExpenseFromCacheAsync(id, tenantId); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error occured while deleting expense from cache"); + } + } #endregion diff --git a/Marco.Pms.Services/Program.cs b/Marco.Pms.Services/Program.cs index a89e16e..2f8bbac 100644 --- a/Marco.Pms.Services/Program.cs +++ b/Marco.Pms.Services/Program.cs @@ -1,6 +1,7 @@ using Marco.Pms.DataAccess.Data; using Marco.Pms.Helpers; using Marco.Pms.Helpers.CacheHelper; +using Marco.Pms.Helpers.Utility; using Marco.Pms.Model.Authentication; using Marco.Pms.Model.Entitlements; using Marco.Pms.Model.Utilities; @@ -186,7 +187,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddScoped(); +builder.Services.AddScoped(); #endregion #region Cache Services diff --git a/Marco.Pms.Services/Service/ExpensesService.cs b/Marco.Pms.Services/Service/ExpensesService.cs index 4da381d..d37142f 100644 --- a/Marco.Pms.Services/Service/ExpensesService.cs +++ b/Marco.Pms.Services/Service/ExpensesService.cs @@ -1,6 +1,6 @@ using AutoMapper; using Marco.Pms.DataAccess.Data; -using Marco.Pms.Helpers; +using Marco.Pms.Helpers.Utility; using Marco.Pms.Model.Dtos.Expenses; using Marco.Pms.Model.Employees; using Marco.Pms.Model.Entitlements; @@ -30,7 +30,7 @@ namespace Marco.Pms.Services.Service private readonly ILoggingService _logger; private readonly S3UploadService _s3Service; private readonly IServiceScopeFactory _serviceScopeFactory; - private readonly UpdateLogHelper _updateLogHelper; + private readonly UtilityMongoDBHelper _updateLogHelper; private readonly CacheUpdateHelper _cache; private readonly IMapper _mapper; private static readonly Guid Draft = Guid.Parse("297e0d8f-f668-41b5-bfea-e03b354251c8"); @@ -40,7 +40,7 @@ namespace Marco.Pms.Services.Service IDbContextFactory dbContextFactory, ApplicationDbContext context, IServiceScopeFactory serviceScopeFactory, - UpdateLogHelper updateLogHelper, + UtilityMongoDBHelper updateLogHelper, CacheUpdateHelper cache, ILoggingService logger, S3UploadService s3Service, @@ -690,6 +690,63 @@ namespace Marco.Pms.Services.Service _logger.LogError(ex, "Concurrency conflict while updating project {ProjectId} ", id); return ApiResponse.ErrorResponse("Conflict occurred.", "This project has been modified by someone else. Please refresh and try again.", 409); } + + if (model.BillAttachments?.Any() ?? false) + { + var newBillAttachments = model.BillAttachments.Where(ba => ba.DocumentId == null && ba.IsActive).ToList(); + if (newBillAttachments.Any()) + { + await ProcessAndUploadAttachmentsAsync(newBillAttachments, existingExpense, loggedInEmployee.Id, tenantId); + await _context.SaveChangesAsync(); + } + var deleteBillAttachments = model.BillAttachments.Where(ba => ba.DocumentId != null && !ba.IsActive).ToList(); + if (deleteBillAttachments.Any()) + { + var documentIds = deleteBillAttachments.Select(d => d.DocumentId).ToList(); + + var attachmentTask = Task.Run(async () => + { + await using var dbContext = await _dbContextFactory.CreateDbContextAsync(); + var attachments = await dbContext.BillAttachments.AsNoTracking().Where(ba => documentIds.Contains(ba.DocumentId)).ToListAsync(); + + dbContext.BillAttachments.RemoveRange(attachments); + await dbContext.SaveChangesAsync(); + }); + var documentsTask = Task.Run(async () => + { + await using var dbContext = await _dbContextFactory.CreateDbContextAsync(); + var documents = await dbContext.Documents.AsNoTracking().Where(ba => documentIds.Contains(ba.Id)).ToListAsync(); + + if (documents.Any()) + { + dbContext.Documents.RemoveRange(documents); + await dbContext.SaveChangesAsync(); + + List deletionObject = new List(); + foreach (var document in documents) + { + deletionObject.Add(new S3DeletionObject + { + Key = document.S3Key + }); + if (!string.IsNullOrWhiteSpace(document.ThumbS3Key) && document.ThumbS3Key != document.S3Key) + { + deletionObject.Add(new S3DeletionObject + { + Key = document.ThumbS3Key + }); + } + } + await _updateLogHelper.PushToS3DeletionAsync(deletionObject); + } + }); + + await Task.WhenAll(attachmentTask, documentsTask); + + } + } + + try { // Task to save the detailed audit log to a separate system (e.g., MongoDB). @@ -718,9 +775,11 @@ namespace Marco.Pms.Services.Service }); }).Unwrap(); - await Task.WhenAll(mongoDBTask, getNextStatusesTask); + var cacheUpdateTask = _cache.ReplaceExpenseAsync(existingExpense); - var nextPossibleStatuses = await getNextStatusesTask; + await Task.WhenAll(mongoDBTask, getNextStatusesTask, cacheUpdateTask); + + var nextPossibleStatuses = getNextStatusesTask.Result; var response = _mapper.Map(existingExpense); if (nextPossibleStatuses != null) diff --git a/Marco.Pms.Services/Service/S3UploadService.cs b/Marco.Pms.Services/Service/S3UploadService.cs index 57a46fa..79c5fa7 100644 --- a/Marco.Pms.Services/Service/S3UploadService.cs +++ b/Marco.Pms.Services/Service/S3UploadService.cs @@ -42,7 +42,8 @@ namespace Marco.Pms.Services.Service if (allowedFilesType == null || !allowedFilesType.Contains(fileType)) { _logger.LogWarning("Unsupported file type. {FileType}", fileType); - throw new InvalidOperationException("Unsupported file type."); + //throw new InvalidOperationException("Unsupported file type."); + return; } fileBytes = Convert.FromBase64String(base64);