From 17df019d55caf8d9712af0022ce0cf3b8517fcaf Mon Sep 17 00:00:00 2001 From: Reginald Cherenfant Jasmin Date: Fri, 10 Nov 2023 13:07:50 -0500 Subject: [PATCH 1/7] Adding CORS policy to all micro-services --- DamageAssesmentApi/DamageAssesment.Api.Answers/Program.cs | 6 +++++- .../DamageAssesment.Api.Attachments/Program.cs | 6 ++++-- DamageAssesmentApi/DamageAssesment.Api.DocuLinks/Program.cs | 5 ++++- DamageAssesmentApi/DamageAssesment.Api.Employees/Program.cs | 6 ++++-- DamageAssesmentApi/DamageAssesment.Api.Locations/Program.cs | 5 ++++- DamageAssesmentApi/DamageAssesment.Api.Questions/Program.cs | 5 ++++- DamageAssesmentApi/DamageAssesment.Api.Responses/Program.cs | 4 ++++ DamageAssesmentApi/DamageAssesment.Api.Surveys/Program.cs | 6 ++++++ 8 files changed, 35 insertions(+), 8 deletions(-) diff --git a/DamageAssesmentApi/DamageAssesment.Api.Answers/Program.cs b/DamageAssesmentApi/DamageAssesment.Api.Answers/Program.cs index 0a38399..1069795 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.Answers/Program.cs +++ b/DamageAssesmentApi/DamageAssesment.Api.Answers/Program.cs @@ -26,6 +26,10 @@ builder.Services.AddDbContext(option => option.UseSqlServer("AnswerConnection"); }); +builder.Services.AddCors(p => p.AddPolicy("DamageAppCorsPolicy", build => { + build.WithOrigins("*").AllowAnyMethod().AllowAnyHeader().AllowAnyOrigin(); +})); + var app = builder.Build(); @@ -35,7 +39,7 @@ if (app.Environment.IsDevelopment()) app.UseSwagger(); app.UseSwaggerUI(); } - +app.UseCors("DamageAppCorsPolicy"); app.UseAuthorization(); app.MapControllers(); diff --git a/DamageAssesmentApi/DamageAssesment.Api.Attachments/Program.cs b/DamageAssesmentApi/DamageAssesment.Api.Attachments/Program.cs index 4fd2e59..3c5a440 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.Attachments/Program.cs +++ b/DamageAssesmentApi/DamageAssesment.Api.Attachments/Program.cs @@ -35,6 +35,9 @@ builder.Services.Configure(o => o.MultipartBodyLengthLimit = int.MaxValue; o.MemoryBufferThreshold = int.MaxValue; }); +builder.Services.AddCors(p => p.AddPolicy("DamageAppCorsPolicy", build => { + build.WithOrigins("*").AllowAnyMethod().AllowAnyHeader().AllowAnyOrigin(); +})); var app = builder.Build(); @@ -44,10 +47,9 @@ if (app.Environment.IsDevelopment()) app.UseSwagger(); app.UseSwaggerUI(); } - +app.UseCors("DamageAppCorsPolicy"); app.UseAuthorization(); app.UseHttpsRedirection(); - app.MapControllers(); app.UseStaticFiles(); app.Run(); diff --git a/DamageAssesmentApi/DamageAssesment.Api.DocuLinks/Program.cs b/DamageAssesmentApi/DamageAssesment.Api.DocuLinks/Program.cs index f28dd76..5dcf1ed 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.DocuLinks/Program.cs +++ b/DamageAssesmentApi/DamageAssesment.Api.DocuLinks/Program.cs @@ -27,6 +27,9 @@ builder.Services.AddDbContext(option => { option.UseSqlServer("DoculinConnection"); }); +builder.Services.AddCors(p => p.AddPolicy("DamageAppCorsPolicy", build => { + build.WithOrigins("*").AllowAnyMethod().AllowAnyHeader().AllowAnyOrigin(); +})); var app = builder.Build(); // Configure the HTTP request pipeline. @@ -35,7 +38,7 @@ if (app.Environment.IsDevelopment()) app.UseSwagger(); app.UseSwaggerUI(); } - +app.UseCors("DamageAppCorsPolicy"); app.UseAuthorization(); app.MapControllers(); diff --git a/DamageAssesmentApi/DamageAssesment.Api.Employees/Program.cs b/DamageAssesmentApi/DamageAssesment.Api.Employees/Program.cs index 7d61871..c93eb2e 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.Employees/Program.cs +++ b/DamageAssesmentApi/DamageAssesment.Api.Employees/Program.cs @@ -26,7 +26,9 @@ builder.Services.AddDbContext(option => { option.UseSqlServer("EmployeeConnection"); }); - +builder.Services.AddCors(p => p.AddPolicy("DamageAppCorsPolicy", build => { + build.WithOrigins("*").AllowAnyMethod().AllowAnyHeader().AllowAnyOrigin(); +})); var app = builder.Build(); // Configure the HTTP request pipeline. @@ -42,7 +44,7 @@ if (app.Environment.IsDevelopment()) employeesProvider.SeedData(); } } - +app.UseCors("DamageAppCorsPolicy"); app.UseAuthorization(); app.MapControllers(); diff --git a/DamageAssesmentApi/DamageAssesment.Api.Locations/Program.cs b/DamageAssesmentApi/DamageAssesment.Api.Locations/Program.cs index f8136bd..22457e4 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.Locations/Program.cs +++ b/DamageAssesmentApi/DamageAssesment.Api.Locations/Program.cs @@ -26,6 +26,9 @@ builder.Services.AddDbContext(option => { option.UseSqlServer("LocationConnection"); }); +builder.Services.AddCors(p => p.AddPolicy("DamageAppCorsPolicy", build => { + build.WithOrigins("*").AllowAnyMethod().AllowAnyHeader().AllowAnyOrigin(); +})); var app = builder.Build(); // Configure the HTTP request pipeline. @@ -43,7 +46,7 @@ if (app.Environment.IsDevelopment()) regionProvider.SeedData(); } } - +app.UseCors("DamageAppCorsPolicy"); app.UseAuthorization(); app.MapControllers(); diff --git a/DamageAssesmentApi/DamageAssesment.Api.Questions/Program.cs b/DamageAssesmentApi/DamageAssesment.Api.Questions/Program.cs index c47a38d..203f267 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.Questions/Program.cs +++ b/DamageAssesmentApi/DamageAssesment.Api.Questions/Program.cs @@ -28,6 +28,9 @@ builder.Services.AddDbContext(option => { option.UseSqlServer("QuestionConnection"); }); +builder.Services.AddCors(p => p.AddPolicy("DamageAppCorsPolicy", build => { + build.WithOrigins("*").AllowAnyMethod().AllowAnyHeader().AllowAnyOrigin(); +})); var app = builder.Build(); // Configure the HTTP request pipeline. @@ -43,7 +46,7 @@ if (app.Environment.IsDevelopment()) questionProvider.SeedData(); } } - +app.UseCors("DamageAppCorsPolicy"); app.UseAuthorization(); app.MapControllers(); diff --git a/DamageAssesmentApi/DamageAssesment.Api.Responses/Program.cs b/DamageAssesmentApi/DamageAssesment.Api.Responses/Program.cs index 8b25674..d09a9d0 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.Responses/Program.cs +++ b/DamageAssesmentApi/DamageAssesment.Api.Responses/Program.cs @@ -47,6 +47,9 @@ builder.Services.AddDbContext(option => { option.UseSqlServer("ResponsesConnection"); }); +builder.Services.AddCors(p => p.AddPolicy("DamageAppCorsPolicy", build => { + build.WithOrigins("*").AllowAnyMethod().AllowAnyHeader().AllowAnyOrigin(); +})); var app = builder.Build(); // Configure the HTTP request pipeline. @@ -56,6 +59,7 @@ if (app.Environment.IsDevelopment()) app.UseSwaggerUI(); } +app.UseCors("DamageAppCorsPolicy"); app.UseAuthorization(); app.MapControllers(); diff --git a/DamageAssesmentApi/DamageAssesment.Api.Surveys/Program.cs b/DamageAssesmentApi/DamageAssesment.Api.Surveys/Program.cs index 178b5fd..0bbba02 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.Surveys/Program.cs +++ b/DamageAssesmentApi/DamageAssesment.Api.Surveys/Program.cs @@ -29,6 +29,11 @@ builder.Services.AddAuthentication(item => }; }); +builder.Services.AddCors(p => p.AddPolicy("DamageAppCorsPolicy", build => { + build.WithOrigins("*").AllowAnyMethod().AllowAnyHeader().AllowAnyOrigin(); +})); + + builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddScoped(); @@ -62,6 +67,7 @@ if (app.Environment.IsDevelopment()) } } +app.UseCors("DamageAppCorsPolicy"); app.UseAuthentication(); app.UseAuthorization(); From 45c61ee729cd1f1bfe5441932dfcc601718d5721 Mon Sep 17 00:00:00 2001 From: Reginald Cherenfant Jasmin Date: Fri, 10 Nov 2023 13:27:31 -0500 Subject: [PATCH 2/7] Addi ng CORS --- DamageAssesmentApi/DamageAssesment.Api.Surveys/Program.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/DamageAssesmentApi/DamageAssesment.Api.Surveys/Program.cs b/DamageAssesmentApi/DamageAssesment.Api.Surveys/Program.cs index 0bbba02..f7f2306 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.Surveys/Program.cs +++ b/DamageAssesmentApi/DamageAssesment.Api.Surveys/Program.cs @@ -33,7 +33,6 @@ builder.Services.AddCors(p => p.AddPolicy("DamageAppCorsPolicy", build => { build.WithOrigins("*").AllowAnyMethod().AllowAnyHeader().AllowAnyOrigin(); })); - builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddScoped(); From e58782243c36f94171077675a0763bd6a939de1f Mon Sep 17 00:00:00 2001 From: Vijay Uppu <913468@dadeschools.net> Date: Mon, 4 Dec 2023 14:56:13 -0500 Subject: [PATCH 3/7] changed download url format and added new endpoint for doculink isactive update --- .../Controllers/AttachmentsController.cs | 63 ++++++++++-- .../Interfaces/IUploadService.cs | 1 + .../Providers/UploadService.cs | 18 ++++ .../DoculinkServiceTest.cs | 22 +++++ .../Controllers/DoculinkController.cs | 99 +++++++++++++++---- .../Interfaces/IDoculinkProvider.cs | 1 + .../Interfaces/IUploadService.cs | 1 + .../Providers/DoculinkProvider.cs | 29 ++++++ .../Providers/UploadService.cs | 18 ++++ 9 files changed, 229 insertions(+), 23 deletions(-) diff --git a/DamageAssesmentApi/DamageAssesment.Api.Attachments/Controllers/AttachmentsController.cs b/DamageAssesmentApi/DamageAssesment.Api.Attachments/Controllers/AttachmentsController.cs index b7cd328..e2fbe25 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.Attachments/Controllers/AttachmentsController.cs +++ b/DamageAssesmentApi/DamageAssesment.Api.Attachments/Controllers/AttachmentsController.cs @@ -139,24 +139,75 @@ namespace DamageAssesment.Api.Attachments.Controllers /// download an existing attachment. /// [HttpGet("attachments/download/{id}")] - public async Task downloadfile1(int id) + public async Task downloadfile(int id) { try { var result = await this.AttachmentProvider.GetDownloadAttachmentAsync(id); - if(!result.IsSuccess) + if (!result.IsSuccess) return NotFound(); - byte[] fileContent = await UploadService.DownloadFile(result.Attachment.URI); - if (fileContent == null || fileContent.Length == 0) + string path = await UploadService.GetFile(result.Attachment.URI); + if (path == null) return NotFound(); - var contentType = "application/octet-stream"; - return File(fileContent, contentType, result.Attachment.FileName); + var contentType = GetContentType(result.Attachment.FileName); + if (contentType == "application/octet-stream") + return PhysicalFile(path, contentType, result.Attachment.FileName); + return PhysicalFile(path, contentType, enableRangeProcessing: true);// result.Attachment.FileName); } catch (Exception ex) { // Handle the exception here or log it return StatusCode(500, "An error occurred: " + ex.Message); } + //try + //{ + // var result = await this.AttachmentProvider.GetDownloadAttachmentAsync(id); + // if(!result.IsSuccess) + // return NotFound(); + // byte[] fileContent = await UploadService.DownloadFile(result.Attachment.URI); + // if (fileContent == null || fileContent.Length == 0) + // return NotFound(); + // var contentType = "application/octet-stream"; + // return File(fileContent, contentType, result.Attachment.FileName); + //} + //catch (Exception ex) + //{ + // // Handle the exception here or log it + // return StatusCode(500, "An error occurred: " + ex.Message); + //} + } + private string GetContentType(string fileName) + { + // You can add more content types based on the file extensions + switch (Path.GetExtension(fileName).ToLower()) + { + //case ".txt": + // return "text/plain"; + case ".jpg": + case ".jpeg": + return "image/jpeg"; + case ".png": + return "image/png"; + case ".gif": + return "image/gif"; + case ".bmp": + return "image/bmp"; + case ".webp": + return "image/webp"; + case ".csv": + return "text/csv"; + case ".pdf": + return "application/pdf"; + case ".docx": + case ".doc": + return "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; + case ".xlsx": + case ".xls": + return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; + // Add more cases as needed + default: + return "application/octet-stream"; + } } /// /// Delete an existing attachment. diff --git a/DamageAssesmentApi/DamageAssesment.Api.Attachments/Interfaces/IUploadService.cs b/DamageAssesmentApi/DamageAssesment.Api.Attachments/Interfaces/IUploadService.cs index f89653f..1d2d0e0 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.Attachments/Interfaces/IUploadService.cs +++ b/DamageAssesmentApi/DamageAssesment.Api.Attachments/Interfaces/IUploadService.cs @@ -8,6 +8,7 @@ namespace DamageAssesment.Api.Attachments.Interfaces List UploadAttachment(int responseId, int counter, List answers); public List UpdateAttachments(int responseId, List answers, IEnumerable attachments); Task DownloadFile(string path); + Task GetFile(string path); void Deletefile(string path); void Movefile(string path); } diff --git a/DamageAssesmentApi/DamageAssesment.Api.Attachments/Providers/UploadService.cs b/DamageAssesmentApi/DamageAssesment.Api.Attachments/Providers/UploadService.cs index 6e03fd0..09711b5 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.Attachments/Providers/UploadService.cs +++ b/DamageAssesmentApi/DamageAssesment.Api.Attachments/Providers/UploadService.cs @@ -24,6 +24,24 @@ namespace DamageAssesment.Api.Attachments.Providers uploadpath = configuration.GetValue("Fileupload:folderpath"); Deletepath = configuration.GetValue("Fileupload:Deletepath"); } + public async Task GetFile(string path) + { + try + { + if (System.IO.File.Exists(path)) + { + return path; + } + + return null; // File not found + } + catch (Exception ex) + { + // Handle or log the exception as needed + throw; + } + + } public async Task DownloadFile(string path) { try diff --git a/DamageAssesmentApi/DamageAssesment.Api.DocuLinks.Test/DoculinkServiceTest.cs b/DamageAssesmentApi/DamageAssesment.Api.DocuLinks.Test/DoculinkServiceTest.cs index a8f45d1..e30925f 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.DocuLinks.Test/DoculinkServiceTest.cs +++ b/DamageAssesmentApi/DamageAssesment.Api.DocuLinks.Test/DoculinkServiceTest.cs @@ -197,7 +197,29 @@ namespace DamageAssesment.Api.DocuLinks.Test var result = (NotFoundResult)await DocumentProvider.DeleteDocument(1); Assert.Equal(404, result.StatusCode); } + [Fact(DisplayName = "Update Document IsActive- Ok case")] + public async Task UpdateDocumentAsync_ShouldReturnStatusCode200() + { + var mockDocumentService = new Mock(); + var mockUploadService = new Mock(); + var mockResponse = await MockData.getOkResponse(1); + mockDocumentService.Setup(service => service.UpdateDocumentAsync(1,true)).ReturnsAsync(mockResponse); + var DocumentProvider = new DoculinkController(mockDocumentService.Object, mockUploadService.Object); + var result = (OkObjectResult)await DocumentProvider.UpdateIsActiveDocument(1,true); + Assert.Equal(200, result.StatusCode); + } + [Fact(DisplayName = "Update Document IsActive - NotFound case")] + public async Task UpdateDocumentAsync_ShouldReturnStatusCode404() + { + var mockDocumentService = new Mock(); + var mockUploadService = new Mock(); + var mockResponse = await MockData.getNotFoundResponse(); + mockDocumentService.Setup(service => service.UpdateDocumentAsync(1,true)).ReturnsAsync(mockResponse); + var DocumentProvider = new DoculinkController(mockDocumentService.Object, mockUploadService.Object); + var result = (NotFoundResult)await DocumentProvider.UpdateIsActiveDocument(1,true); + Assert.Equal(404, result.StatusCode); + } // Link Type Test cases diff --git a/DamageAssesmentApi/DamageAssesment.Api.DocuLinks/Controllers/DoculinkController.cs b/DamageAssesmentApi/DamageAssesment.Api.DocuLinks/Controllers/DoculinkController.cs index 1d2e3de..4cee929 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.DocuLinks/Controllers/DoculinkController.cs +++ b/DamageAssesmentApi/DamageAssesment.Api.DocuLinks/Controllers/DoculinkController.cs @@ -13,11 +13,11 @@ namespace DamageAssesment.Api.DocuLinks.Controllers private readonly IDoculinkProvider documentsProvider; private readonly IUploadService uploadService; - public DoculinkController(IDoculinkProvider documentsProvider,IUploadService uploadService) + public DoculinkController(IDoculinkProvider documentsProvider, IUploadService uploadService) { this.documentsProvider = documentsProvider; - this.uploadService = uploadService; + this.uploadService = uploadService; } /// @@ -41,7 +41,7 @@ namespace DamageAssesment.Api.DocuLinks.Controllers [HttpGet] [Route("doculinks/types/{id}")] [Route("doculinks/types/{id}/{language:alpha}")] - public async Task GetLinkTypeAsync(int id,string? language) + public async Task GetLinkTypeAsync(int id, string? language) { var result = await this.documentsProvider.GetLinkTypeAsync(id, language); if (result.IsSuccess) @@ -55,11 +55,11 @@ namespace DamageAssesment.Api.DocuLinks.Controllers /// [HttpPut] [Route("doculinks/types/{id}")] - public async Task UpdateLinkType(int id,Models.LinkType linkType) + public async Task UpdateLinkType(int id, Models.LinkType linkType) { if (linkType != null) { - var result = await this.documentsProvider.UpdateLinkTypeAsync(id,linkType); + var result = await this.documentsProvider.UpdateLinkTypeAsync(id, linkType); if (result.IsSuccess) { return Ok(result.LinkType); @@ -107,24 +107,75 @@ namespace DamageAssesment.Api.DocuLinks.Controllers /// download an existing attachment. /// [HttpGet("doculinks/download/{id}")] - public async Task downloadfile1(int id) + public async Task downloadfile(int id) { try { var result = await this.documentsProvider.GetDownloadAttachmentAsync(id); if (!result.IsSuccess) return NotFound(); - byte[] fileContent = await uploadService.DownloadFile(result.DoculinkAttachments.Path); - if (fileContent == null || fileContent.Length == 0) + string path = await uploadService.GetFile(result.DoculinkAttachments.Path); + if (path == null) return NotFound(); - var contentType = "application/octet-stream"; - return File(fileContent, contentType, result.DoculinkAttachments.docName); + var contentType = GetContentType(result.DoculinkAttachments.docName); + if (contentType == "application/octet-stream") + return PhysicalFile(path, contentType, result.DoculinkAttachments.docName); + return PhysicalFile(path, contentType, enableRangeProcessing: true); } catch (Exception ex) { // Handle the exception here or log it return StatusCode(500, "An error occurred: " + ex.Message); } + //try + //{ + // var result = await this.documentsProvider.GetDownloadAttachmentAsync(id); + // if (!result.IsSuccess) + // return NotFound(); + // byte[] fileContent = await uploadService.DownloadFile(result.DoculinkAttachments.Path); + // if (fileContent == null || fileContent.Length == 0) + // return NotFound(); + // var contentType = "application/octet-stream"; + // return File(fileContent, contentType, result.DoculinkAttachments.docName); + //} + //catch (Exception ex) + //{ + // // Handle the exception here or log it + // return StatusCode(500, "An error occurred: " + ex.Message); + //} + } + private string GetContentType(string fileName) + { + // You can add more content types based on the file extensions + switch (Path.GetExtension(fileName).ToLower()) + { + //case ".txt": + // return "text/plain"; + case ".jpg": + case ".jpeg": + return "image/jpeg"; + case ".png": + return "image/png"; + case ".gif": + return "image/gif"; + case ".bmp": + return "image/bmp"; + case ".webp": + return "image/webp"; + case ".csv": + return "text/csv"; + case ".pdf": + return "application/pdf"; + case ".docx": + case ".doc": + return "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; + case ".xlsx": + case ".xls": + return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; + // Add more cases as needed + default: + return "application/octet-stream"; + } } /// /// Get all Doculink. @@ -134,7 +185,7 @@ namespace DamageAssesment.Api.DocuLinks.Controllers [Route("doculinks/{linktype:alpha}")] [Route("doculinks/{linktype:alpha}/{language:alpha}")] [HttpGet] - public async Task GetDocumentsAsync(string? linktype, string? language,bool? isactive) + public async Task GetDocumentsAsync(string? linktype, string? language, bool? isactive) { var result = await this.documentsProvider.GetdocumentsByLinkAsync(linktype, language, isactive); if (result.IsSuccess) @@ -152,7 +203,7 @@ namespace DamageAssesment.Api.DocuLinks.Controllers [HttpGet] public async Task GetDocumentsByActiveAsync(string? linktype, string? language) { - var result = await this.documentsProvider.GetdocumentsByLinkAsync(linktype, language,true); + var result = await this.documentsProvider.GetdocumentsByLinkAsync(linktype, language, true); if (result.IsSuccess) { return Ok(result.documents); @@ -181,7 +232,7 @@ namespace DamageAssesment.Api.DocuLinks.Controllers [Route("doculinks/{id}")] [Route("doculinks/{id}/{linktype:alpha}")] [Route("doculinks/{id}/{linktype:alpha}/{language:alpha}")] - public async Task GetDocumentAsync(int id,string? linktype, string? language) + public async Task GetDocumentAsync(int id, string? linktype, string? language) { var result = await this.documentsProvider.GetDocumentAsync(id, linktype, language); if (result.IsSuccess) @@ -195,7 +246,7 @@ namespace DamageAssesment.Api.DocuLinks.Controllers /// [HttpPut] [Route("doculinks/{id}")] - public async Task UpdateDocument(int id,ReqDoculink documentInfo) + public async Task UpdateDocument(int id, ReqDoculink documentInfo) { if (documentInfo != null) { @@ -203,7 +254,7 @@ namespace DamageAssesment.Api.DocuLinks.Controllers if (dbdoc.IsSuccess) { var documents = await this.documentsProvider.GetDocumentCounter(); - Models.Doculink DocuLink= uploadService.UpdateDocuments(documents.counter,dbdoc.Document, documentInfo); + Models.Doculink DocuLink = uploadService.UpdateDocuments(documents.counter, dbdoc.Document, documentInfo); var result = await this.documentsProvider.UpdateDocumentAsync(id, DocuLink); if (result.IsSuccess) { @@ -216,6 +267,20 @@ namespace DamageAssesment.Api.DocuLinks.Controllers return BadRequest(documentInfo); } /// + /// update existing doclink isactive field. + /// + [HttpPut] + [Route("doculinks/{id}/{isactive}")] + public async Task UpdateIsActiveDocument(int id, bool isactive) + { + var result = await this.documentsProvider.UpdateDocumentAsync(id, isactive); + if (result.IsSuccess) + { + return Ok(result.Document); + } + return NotFound(); + } + /// /// Create new doclink. /// [HttpPost] @@ -227,7 +292,7 @@ namespace DamageAssesment.Api.DocuLinks.Controllers if (documentInfo != null) { var documents = await this.documentsProvider.GetDocumentCounter(); - Models.Doculink DocuLink= uploadService.UploadDocument(documents.counter, documentInfo); + Models.Doculink DocuLink = uploadService.UploadDocument(documents.counter, documentInfo); var result = await this.documentsProvider.PostDocumentAsync(DocuLink); if (result.IsSuccess) { @@ -262,6 +327,6 @@ namespace DamageAssesment.Api.DocuLinks.Controllers } return NotFound(); } - + } } diff --git a/DamageAssesmentApi/DamageAssesment.Api.DocuLinks/Interfaces/IDoculinkProvider.cs b/DamageAssesmentApi/DamageAssesment.Api.DocuLinks/Interfaces/IDoculinkProvider.cs index 22f0fe6..d873bbe 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.DocuLinks/Interfaces/IDoculinkProvider.cs +++ b/DamageAssesmentApi/DamageAssesment.Api.DocuLinks/Interfaces/IDoculinkProvider.cs @@ -11,6 +11,7 @@ namespace DamageAssesment.Api.DocuLinks.Interfaces Task<(bool IsSuccess, IEnumerable documents, string ErrorMessage)> GetdocumentsByLinkTypeIdAsync(int? linkTypeId, string? language, bool? isactive); Task<(bool IsSuccess, Models.ResDoculink Document, string ErrorMessage)> PostDocumentAsync(Models.Doculink Document); Task<(bool IsSuccess, Models.ResDoculink Document, string ErrorMessage)> UpdateDocumentAsync(int id, Models.Doculink Document); + Task<(bool IsSuccess, Models.ResDoculink Document, string ErrorMessage)> UpdateDocumentAsync(int id, bool isactive); Task<(bool IsSuccess, Models.ResDoculink Document, string ErrorMessage)> DeleteDocumentAsync(int id); Task<(bool IsSuccess, Models.DoculinkAttachments DoculinkAttachments, string Path)> GetDownloadAttachmentAsync(int id); Task<(bool IsSuccess, int counter, string message)> GetDocumentCounter(); diff --git a/DamageAssesmentApi/DamageAssesment.Api.DocuLinks/Interfaces/IUploadService.cs b/DamageAssesmentApi/DamageAssesment.Api.DocuLinks/Interfaces/IUploadService.cs index 4fbc5ee..63c00aa 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.DocuLinks/Interfaces/IUploadService.cs +++ b/DamageAssesmentApi/DamageAssesment.Api.DocuLinks/Interfaces/IUploadService.cs @@ -8,6 +8,7 @@ namespace DamageAssesment.Api.DocuLinks.Interfaces public Models.Doculink UpdateDocuments(int counter, Models.Doculink document, ReqDoculink documentInfo); void Deletefile(string path); Task DownloadFile(string path); + Task GetFile(string path); void Movefile(string path); } } diff --git a/DamageAssesmentApi/DamageAssesment.Api.DocuLinks/Providers/DoculinkProvider.cs b/DamageAssesmentApi/DamageAssesment.Api.DocuLinks/Providers/DoculinkProvider.cs index 30d3103..b7c119d 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.DocuLinks/Providers/DoculinkProvider.cs +++ b/DamageAssesmentApi/DamageAssesment.Api.DocuLinks/Providers/DoculinkProvider.cs @@ -453,6 +453,35 @@ namespace DamageAssesment.Api.DocuLinks.Providers return (false, null, ex.Message); } } + public async Task<(bool IsSuccess, Models.ResDoculink Document, string ErrorMessage)> UpdateDocumentAsync(int id,bool isactive) + { + + try + { + Db.Doculink Document = DocumentDbContext.Documents.AsNoTracking().Where(a => a.Id == id).FirstOrDefault(); + if (Document == null) + { + return (false, null, "Not Found"); + } + Document.IsActive = isactive; + DocumentDbContext.Documents.Update(Document); + DocumentDbContext.SaveChanges(); + var result = mapper.Map(Document); + var multilan = CreateMultiLanguageObject(GetDocumentTranslations(Document.Id, "")); + result.titles = multilan.titles; + result.description = multilan.description; + result.linktypes = CreateMultiLanguageLinkTypeObject(GetLinkTypeTranslations(result.linkTypeId, "")); + result.doclinksAttachments = mapper.Map, List>( + DocumentDbContext.DoclinksAttachments.AsNoTracking().Where(a => a.DocumentId == id).ToList()); + return (true, result, $"DocumentId {id} deleted Successfuly"); + } + catch (Exception ex) + { + + logger?.LogError(ex.ToString()); + return (false, null, ex.Message); + } + } public async Task<(bool IsSuccess, Models.ResDoculink Document, string ErrorMessage)> DeleteDocumentAsync(int id) { diff --git a/DamageAssesmentApi/DamageAssesment.Api.DocuLinks/Providers/UploadService.cs b/DamageAssesmentApi/DamageAssesment.Api.DocuLinks/Providers/UploadService.cs index 12e29c3..5d670b7 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.DocuLinks/Providers/UploadService.cs +++ b/DamageAssesmentApi/DamageAssesment.Api.DocuLinks/Providers/UploadService.cs @@ -25,6 +25,24 @@ namespace DamageAssesment.Api.DocuLinks.Providers uploadpath = configuration.GetValue("Fileupload:folderpath"); Deletepath = configuration.GetValue("Fileupload:Deletepath"); } + public async Task GetFile(string path) + { + try + { + if (System.IO.File.Exists(path)) + { + return path; + } + + return null; // File not found + } + catch (Exception ex) + { + // Handle or log the exception as needed + throw; + } + + } public async Task DownloadFile(string path) { try From 5cc331202b44e0c3647ec2b36800f6f4751e41be Mon Sep 17 00:00:00 2001 From: Reginald Cherenfant Jasmin Date: Thu, 7 Dec 2023 01:41:00 -0500 Subject: [PATCH 4/7] mergr conflict resolve --- .../Program.cs | 147 ++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Program.cs diff --git a/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Program.cs b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Program.cs new file mode 100644 index 0000000..8fbea9f --- /dev/null +++ b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Program.cs @@ -0,0 +1,147 @@ +using DamageAssesment.Api.UsersAccess.Db; +using DamageAssesment.Api.UsersAccess.Interfaces; +using DamageAssesment.Api.UsersAccess.Providers; +using DamageAssesment.Api.UsersAccess.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.IdentityModel.Tokens; +using System.Text; +using Polly; +using DamageAssesment.Api.UsersAccess.Services; +using Microsoft.OpenApi.Models; +using System.Reflection; +using Microsoft.AspNetCore.Authorization; + +const int maxApiCallRetries = 3; +const int intervalToRetry = 2; //2 seconds +const int maxRetryForCircuitBraker = 5; +const int intervalForCircuitBraker = 5; //5 seconds + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +var authkey = builder.Configuration.GetValue("JwtSettings:securitykey"); + + +builder.Services.AddAuthentication(). + AddJwtBearer("DamageApp", item => +{ + + item.RequireHttpsMetadata = true; + item.SaveToken = true; + item.TokenValidationParameters = new TokenValidationParameters() + { + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(authkey)), + ValidateIssuer = false, + ValidateAudience = false, + ClockSkew = TimeSpan.Zero + }; +}).AddJwtBearer("Dadeschools", options => +{ + options.Authority = builder.Configuration["Dadeschools:Authority"]; + options.TokenValidationParameters.ValidTypes = new[] { "at+jwt" }; + options.TokenValidationParameters.ValidateAudience = false; +}); + + +builder.Services.AddAuthorization(options => +{ + var DamageAppPolicy = new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .AddAuthenticationSchemes("DamageApp") + .Build(); + var DadeschoolsPolicy = new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .AddAuthenticationSchemes("Dadeschools") + .Build(); + + var allPolicy = new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .AddAuthenticationSchemes("DamageApp", "Dadeschools") + .Build(); + options.AddPolicy("DamageApp", DamageAppPolicy); + options.AddPolicy("Dadeschools", DadeschoolsPolicy); + options.AddPolicy("AllPolicies", allPolicy); + options.DefaultPolicy = options.GetPolicy("DamageApp")!; +}); + +var _jwtsettings = builder.Configuration.GetSection("JwtSettings"); +builder.Services.Configure(_jwtsettings); + +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); + +builder.Services.AddHttpClient(). + AddTransientHttpErrorPolicy(policy => policy.WaitAndRetryAsync(maxApiCallRetries, _ => TimeSpan.FromSeconds(intervalToRetry))). + AddTransientHttpErrorPolicy(policy => policy.CircuitBreakerAsync(maxRetryForCircuitBraker, TimeSpan.FromSeconds(intervalForCircuitBraker))); + +builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies()); +builder.Services.AddEndpointsApiExplorer(); +//builder.Services.AddSwaggerGen(); + +builder.Services.AddSwaggerGen(options => +{ + + // Include XML comments from your assembly + var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; + var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); + //options.IncludeXmlComments(xmlPath); + + OpenApiSecurityScheme securityDefinition = new OpenApiSecurityScheme() + { + Name = "Bearer", + BearerFormat = "JWT", + Scheme = "bearer", + Description = "Specify the authorization token.", + In = ParameterLocation.Header, + Type = SecuritySchemeType.Http, + }; + + options.AddSecurityDefinition("jwt_auth", securityDefinition); + + // Make sure swagger UI requires a Bearer token specified + OpenApiSecurityScheme securityScheme = new OpenApiSecurityScheme() + { + Reference = new OpenApiReference() + { + Id = "jwt_auth", + Type = ReferenceType.SecurityScheme + } + }; + + OpenApiSecurityRequirement securityRequirements = new OpenApiSecurityRequirement() + { + {securityScheme, new string[] { }}, + }; + + options.AddSecurityRequirement(securityRequirements); +}); + +builder.Services.AddDbContext(option => +{ + option.UseInMemoryDatabase("UsersAccess"); +}); +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); + + using (var serviceScope = app.Services.CreateScope()) + { + var services = serviceScope.ServiceProvider; + var usersAccessProvider = services.GetRequiredService(); + usersAccessProvider.seedData(); + } +} + +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapControllers(); +app.Run(); From d7005479c5c607dc8bfec0c1b9325f3e42f8ae05 Mon Sep 17 00:00:00 2001 From: Reginald Cherenfant Jasmin Date: Thu, 7 Dec 2023 02:16:00 -0500 Subject: [PATCH 5/7] Adding endpoint for mutiple questions update --- .../Program.cs | 4 +--- .../DamageAssesment.Api.Employees/Program.cs | 4 +--- .../DamageAssesment.Api.Locations/Program.cs | 4 +--- .../Controllers/QuestionsController.cs | 22 +++++++++++++++++++ .../Interfaces/IQuestionsProvider.cs | 1 + .../DamageAssesment.Api.Questions/Program.cs | 4 +--- .../Providers/QuestionsProvider.cs | 16 ++++++++++++++ 7 files changed, 43 insertions(+), 12 deletions(-) diff --git a/DamageAssesmentApi/DamageAssesment.Api.Attachments/Program.cs b/DamageAssesmentApi/DamageAssesment.Api.Attachments/Program.cs index ba0f71c..c415f31 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.Attachments/Program.cs +++ b/DamageAssesmentApi/DamageAssesment.Api.Attachments/Program.cs @@ -54,11 +54,9 @@ if (app.Environment.IsDevelopment()) app.UseSwagger(); app.UseSwaggerUI(); } -<<<<<<< HEAD -======= // Enable CORS, authentication, and authorization middleware. ->>>>>>> 9ec9b8b96fc7c5767fcddf1e4e52bde203fcf619 + app.UseCors("DamageAppCorsPolicy"); app.UseAuthorization(); app.UseHttpsRedirection(); diff --git a/DamageAssesmentApi/DamageAssesment.Api.Employees/Program.cs b/DamageAssesmentApi/DamageAssesment.Api.Employees/Program.cs index 01d561b..b1af271 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.Employees/Program.cs +++ b/DamageAssesmentApi/DamageAssesment.Api.Employees/Program.cs @@ -50,14 +50,12 @@ if (app.Environment.IsDevelopment()) employeesProvider.SeedData(); } } -<<<<<<< HEAD + app.UseCors("DamageAppCorsPolicy"); -======= // Enable CORS, authentication, and authorization middleware. app.UseCors("DamageAppCorsPolicy"); ->>>>>>> 9ec9b8b96fc7c5767fcddf1e4e52bde203fcf619 app.UseAuthorization(); app.MapControllers(); diff --git a/DamageAssesmentApi/DamageAssesment.Api.Locations/Program.cs b/DamageAssesmentApi/DamageAssesment.Api.Locations/Program.cs index d2ef35b..fa92c7b 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.Locations/Program.cs +++ b/DamageAssesmentApi/DamageAssesment.Api.Locations/Program.cs @@ -51,14 +51,12 @@ if (app.Environment.IsDevelopment()) regionProvider.SeedData(); } } -<<<<<<< HEAD + app.UseCors("DamageAppCorsPolicy"); -======= // Enable CORS, authentication, and authorization middleware. app.UseCors("DamageAppCorsPolicy"); ->>>>>>> 9ec9b8b96fc7c5767fcddf1e4e52bde203fcf619 app.UseAuthorization(); app.MapControllers(); diff --git a/DamageAssesmentApi/DamageAssesment.Api.Questions/Controllers/QuestionsController.cs b/DamageAssesmentApi/DamageAssesment.Api.Questions/Controllers/QuestionsController.cs index c73ff8a..ae17006 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.Questions/Controllers/QuestionsController.cs +++ b/DamageAssesmentApi/DamageAssesment.Api.Questions/Controllers/QuestionsController.cs @@ -1,4 +1,5 @@ using DamageAssesment.Api.Questions.Interfaces; +using DamageAssesment.Api.Questions.Models; using Microsoft.AspNetCore.Mvc; namespace DamageAssesment.Api.Questions.Controllers @@ -108,6 +109,27 @@ namespace DamageAssesment.Api.Questions.Controllers } return CreatedAtRoute("DefaultApi",questions); } + + /// + /// POST request for creating a multiple question (multilingual). + /// + [HttpPost("questions/multiple/{surveyid}")] + public async Task UpdateQuestions(int surveyid, List questions) + { + if (questions != null) + { + var result = await this.questionsProvider.PutQuestionsAsync(surveyid,questions); + if (result.IsSuccess) + { + return Ok(result.Question); + } + if (result.ErrorMessage == "Not Found") + return NotFound(result.ErrorMessage); + + return BadRequest(result.ErrorMessage); + } + return CreatedAtRoute("DefaultApi", questions); + } /// /// POST request for creating a new question (multilingual). /// diff --git a/DamageAssesmentApi/DamageAssesment.Api.Questions/Interfaces/IQuestionsProvider.cs b/DamageAssesmentApi/DamageAssesment.Api.Questions/Interfaces/IQuestionsProvider.cs index 4fcea32..eb2be52 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.Questions/Interfaces/IQuestionsProvider.cs +++ b/DamageAssesmentApi/DamageAssesment.Api.Questions/Interfaces/IQuestionsProvider.cs @@ -9,6 +9,7 @@ namespace DamageAssesment.Api.Questions.Interfaces Task<(bool IsSuccess, List SurveyQuestions, string ErrorMessage)> GetSurveyQuestionAsync(int surveyId,string language); Task<(bool IsSuccess, Models.MultiLanguage Question, string ErrorMessage)> PostQuestionAsync(Models.Question Question); Task<(bool IsSuccess, IEnumerable Question, string ErrorMessage)> PostQuestionsAsync(List Questions); + Task<(bool IsSuccess, IEnumerable Question, string ErrorMessage)> PutQuestionsAsync(int surveyId, List Questions); Task<(bool IsSuccess, Models.MultiLanguage Question, string ErrorMessage)> UpdateQuestionAsync(Models.Question Question); Task<(bool IsSuccess, Models.MultiLanguage Question, string ErrorMessage)> DeleteQuestionAsync(int id); diff --git a/DamageAssesmentApi/DamageAssesment.Api.Questions/Program.cs b/DamageAssesmentApi/DamageAssesment.Api.Questions/Program.cs index 8908e35..49bcda4 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.Questions/Program.cs +++ b/DamageAssesmentApi/DamageAssesment.Api.Questions/Program.cs @@ -52,11 +52,9 @@ if (app.Environment.IsDevelopment()) questionProvider.SeedData(); } } -<<<<<<< HEAD -======= + // Enable CORS, authentication, and authorization middleware. ->>>>>>> 9ec9b8b96fc7c5767fcddf1e4e52bde203fcf619 app.UseCors("DamageAppCorsPolicy"); app.UseAuthorization(); diff --git a/DamageAssesmentApi/DamageAssesment.Api.Questions/Providers/QuestionsProvider.cs b/DamageAssesmentApi/DamageAssesment.Api.Questions/Providers/QuestionsProvider.cs index d6bfebc..b0b002a 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.Questions/Providers/QuestionsProvider.cs +++ b/DamageAssesmentApi/DamageAssesment.Api.Questions/Providers/QuestionsProvider.cs @@ -376,6 +376,22 @@ namespace DamageAssesment.Api.Questions.Providers return (false, null, ex.Message); } } + + public async Task<(bool IsSuccess, IEnumerable Question, string ErrorMessage)> PutQuestionsAsync(int surveyId, List Questions) + { + try + { + questionDbContext.Questions.ToList().RemoveAll(s=> s.SurveyId == surveyId); + questionDbContext.SaveChanges(); + var response = await PostQuestionsAsync(Questions); + return (response); + } + catch (Exception ex) + { + logger?.LogError(ex.ToString()); + return (false, null, ex.Message); + } + } public async Task<(bool IsSuccess, Models.MultiLanguage Question, string ErrorMessage)> UpdateQuestionAsync(Models.Question Question) { try From 5ad4478670cbdb1d5d454e830ee1efbbd714480b Mon Sep 17 00:00:00 2001 From: Reginald Cherenfant Jasmin Date: Thu, 7 Dec 2023 02:28:57 -0500 Subject: [PATCH 6/7] Merge Conflict resolution --- DamageAssesmentApi/DamageAssesment.Api.Answers/Program.cs | 4 ---- DamageAssesmentApi/DamageAssesment.Api.DocuLinks/Program.cs | 4 +--- DamageAssesmentApi/DamageAssesment.Api.Responses/Program.cs | 4 ---- 3 files changed, 1 insertion(+), 11 deletions(-) diff --git a/DamageAssesmentApi/DamageAssesment.Api.Answers/Program.cs b/DamageAssesmentApi/DamageAssesment.Api.Answers/Program.cs index e97bd90..2ffd8cf 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.Answers/Program.cs +++ b/DamageAssesmentApi/DamageAssesment.Api.Answers/Program.cs @@ -44,12 +44,8 @@ if (app.Environment.IsDevelopment()) app.UseSwagger(); app.UseSwaggerUI(); } -<<<<<<< HEAD app.UseCors("DamageAppCorsPolicy"); app.UseAuthorization(); -======= - ->>>>>>> 9ec9b8b96fc7c5767fcddf1e4e52bde203fcf619 // Enable CORS, authentication, and authorization middleware. app.UseCors("DamageAppCorsPolicy"); diff --git a/DamageAssesmentApi/DamageAssesment.Api.DocuLinks/Program.cs b/DamageAssesmentApi/DamageAssesment.Api.DocuLinks/Program.cs index e7fe14c..aec0623 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.DocuLinks/Program.cs +++ b/DamageAssesmentApi/DamageAssesment.Api.DocuLinks/Program.cs @@ -44,12 +44,10 @@ if (app.Environment.IsDevelopment()) app.UseSwagger(); app.UseSwaggerUI(); } -<<<<<<< HEAD -======= // Enable CORS, authentication, and authorization middleware. ->>>>>>> 9ec9b8b96fc7c5767fcddf1e4e52bde203fcf619 + app.UseCors("DamageAppCorsPolicy"); app.UseAuthorization(); diff --git a/DamageAssesmentApi/DamageAssesment.Api.Responses/Program.cs b/DamageAssesmentApi/DamageAssesment.Api.Responses/Program.cs index 7304cfd..1721409 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.Responses/Program.cs +++ b/DamageAssesmentApi/DamageAssesment.Api.Responses/Program.cs @@ -64,10 +64,6 @@ if (app.Environment.IsDevelopment()) app.UseSwaggerUI(); } -<<<<<<< HEAD -======= -// Enable CORS, authentication, and authorization middleware. ->>>>>>> 9ec9b8b96fc7c5767fcddf1e4e52bde203fcf619 app.UseCors("DamageAppCorsPolicy"); app.UseAuthorization(); From f967e38c81444fbea9dadd2ffc1a4be99fc209a6 Mon Sep 17 00:00:00 2001 From: Reginald Cherenfant Jasmin Date: Thu, 7 Dec 2023 02:48:07 -0500 Subject: [PATCH 7/7] Changing POST to PUT --- .../Controllers/QuestionsController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DamageAssesmentApi/DamageAssesment.Api.Questions/Controllers/QuestionsController.cs b/DamageAssesmentApi/DamageAssesment.Api.Questions/Controllers/QuestionsController.cs index ae17006..dbd4170 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.Questions/Controllers/QuestionsController.cs +++ b/DamageAssesmentApi/DamageAssesment.Api.Questions/Controllers/QuestionsController.cs @@ -113,7 +113,7 @@ namespace DamageAssesment.Api.Questions.Controllers /// /// POST request for creating a multiple question (multilingual). /// - [HttpPost("questions/multiple/{surveyid}")] + [HttpPut("questions/multiple/{surveyid}")] public async Task UpdateQuestions(int surveyid, List questions) { if (questions != null)