From 620f35fe25b4b1d9c66b27f1038eba1bdc01b71d Mon Sep 17 00:00:00 2001 From: David Pavlovski Date: Wed, 26 Nov 2025 11:21:05 +0100 Subject: [PATCH 1/7] feat:data shaping --- .../Extensions/IEnumerableExtensions.cs | 51 +++++++++++++++++++ .../Extensions/ObjectExtensions.cs | 46 +++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 Dappi.HeadlessCms/Extensions/IEnumerableExtensions.cs create mode 100644 Dappi.HeadlessCms/Extensions/ObjectExtensions.cs diff --git a/Dappi.HeadlessCms/Extensions/IEnumerableExtensions.cs b/Dappi.HeadlessCms/Extensions/IEnumerableExtensions.cs new file mode 100644 index 00000000..f24bd0d1 --- /dev/null +++ b/Dappi.HeadlessCms/Extensions/IEnumerableExtensions.cs @@ -0,0 +1,51 @@ +using System.Dynamic; +using System.Reflection; + +namespace Dappi.HeadlessCms.Extensions +{ + public static class IEnumerableExtensions + { + public static IEnumerable ShapeData(this IEnumerable source, string? fields) + { + ArgumentNullException.ThrowIfNull(source); + var objectList = new List(); + var propertyInfoList = new List(); + if (string.IsNullOrEmpty(fields)) + { + var propertyInfos = + typeof(TSource).GetProperties(BindingFlags.IgnoreCase | + BindingFlags.Public | + BindingFlags.Instance); + propertyInfoList.AddRange(propertyInfos); + } + else + { + foreach (var field in fields.Split(',')) + { + var propertyName = field.Trim(); + var propertyInfo = typeof(TSource).GetProperty(propertyName, + BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance); + if (propertyInfo is null) + { + throw new Exception($"Property {propertyName} not found in {typeof(TSource).FullName}"); + } + + propertyInfoList.Add(propertyInfo); + } + } + + foreach (var sourceObject in source) + { + var expandoObject = new ExpandoObject(); + foreach (var propertyInfo in propertyInfoList) + { + var propertyValue = propertyInfo.GetValue(sourceObject); + ((IDictionary)expandoObject).Add(propertyInfo.Name, propertyValue); + } + objectList.Add(expandoObject); + } + + return objectList; + } + } +} \ No newline at end of file diff --git a/Dappi.HeadlessCms/Extensions/ObjectExtensions.cs b/Dappi.HeadlessCms/Extensions/ObjectExtensions.cs new file mode 100644 index 00000000..422d3aab --- /dev/null +++ b/Dappi.HeadlessCms/Extensions/ObjectExtensions.cs @@ -0,0 +1,46 @@ +using System.Dynamic; +using System.Reflection; + +namespace Dappi.HeadlessCms.Extensions +{ + public static class ObjectExtensions + { + public static ExpandoObject ShapeObject(this TSource source, string? fields) + { + ArgumentNullException.ThrowIfNull(source); + + var dataShapedObject = new ExpandoObject(); + + if (string.IsNullOrEmpty(fields)) + { + var propertyInfos = typeof(TSource).GetProperties(BindingFlags.IgnoreCase + | BindingFlags.Public + | BindingFlags.Instance); + foreach (var propertyInfo in propertyInfos) + { + var propertyValue = propertyInfo.GetValue(source); + ((IDictionary)dataShapedObject).Add(propertyInfo.Name, propertyValue); + } + return dataShapedObject; + } + + foreach (var field in fields.Split(',')) + { + var propName = field.Trim(); + var propertyInfo = typeof(TSource).GetProperty(propName, + BindingFlags.IgnoreCase + | BindingFlags.Public + | BindingFlags.Instance); + if (propertyInfo is null) + { + throw new Exception($"Property {propName} not found in {typeof(TSource).FullName}"); + } + + var propertyValue = propertyInfo.GetValue(source); + ((IDictionary)dataShapedObject).Add(propertyInfo.Name, propertyValue); + } + + return dataShapedObject; + } + } +} \ No newline at end of file From 3e17b1e6dcc609d6a336145efdd454783561ea84 Mon Sep 17 00:00:00 2001 From: David Pavlovski Date: Wed, 26 Nov 2025 11:46:57 +0100 Subject: [PATCH 2/7] feat: implement data shaping in crud generator --- Dappi.SourceGenerator/CrudGenerator.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Dappi.SourceGenerator/CrudGenerator.cs b/Dappi.SourceGenerator/CrudGenerator.cs index a8658301..fe016509 100644 --- a/Dappi.SourceGenerator/CrudGenerator.cs +++ b/Dappi.SourceGenerator/CrudGenerator.cs @@ -43,6 +43,7 @@ protected override void Execute(SourceProductionContext context, using System.Text.Json.Nodes; using Dappi.HeadlessCms.Models; using Dappi.HeadlessCms.Interfaces; +using Dappi.HeadlessCms.Extensions; using {item.ModelNamespace}; using {item.RootNamespace}.Filtering; using {item.RootNamespace}.HelperDtos; @@ -50,6 +51,7 @@ protected override void Execute(SourceProductionContext context, using Microsoft.AspNetCore.Authorization; using System.IO; using System.Reflection; +using System.Dynamic; /* ==== area for testing ==== @@ -69,7 +71,7 @@ public partial class {item.ClassName}Controller( {{ [HttpGet] {PropagateDappiAuthorizationTags(item.AuthorizeAttributes, AuthorizeMethods.Get)} - public async Task Get{item.ClassName.Pluralize()}([FromQuery] {item.ClassName}Filter? filter) + public async Task Get{item.ClassName.Pluralize()}([FromQuery] {item.ClassName}Filter? filter, [FromQuery] string? fields = null) {{ var query = dbContext.{item.ClassName.Pluralize()}.AsNoTracking().AsQueryable(); @@ -91,9 +93,9 @@ public partial class {item.ClassName}Controller( .Take(filter.Limit) .ToListAsync(); - var listDto = new ListResponseDTO<{item.ClassName}> + var listDto = new ListResponseDTO {{ - Data = data, + Data = data.ShapeData(fields), Limit = filter.Limit, Offset = filter.Offset, Total = total @@ -104,7 +106,7 @@ public partial class {item.ClassName}Controller( [HttpGet(""{{id}}"")] {PropagateDappiAuthorizationTags(item.AuthorizeAttributes, AuthorizeMethods.Get)} - public async Task Get{item.ClassName}(Guid id) + public async Task Get{item.ClassName}(Guid id, [FromQuery] string? fields = null) {{ if (id == Guid.Empty) return BadRequest(); @@ -119,7 +121,7 @@ public partial class {item.ClassName}Controller( if (result is null) return NotFound(); - return Ok(result); + return Ok(result.ShapeObject(fields)); }} [HttpPost] From fef752ff3ce49b4ff94fb92f917a1c9aa18fd812 Mon Sep 17 00:00:00 2001 From: David Pavlovski Date: Thu, 27 Nov 2025 09:30:15 +0100 Subject: [PATCH 3/7] custom exception for non existing properties --- .../Exceptions/PropertyNotFoundException.cs | 18 ++++ ...eExtensions.cs => EnumerableExtensions.cs} | 5 +- .../Extensions/ObjectExtensions.cs | 3 +- Dappi.SourceGenerator/CrudGenerator.cs | 83 +++++++++++-------- 4 files changed, 72 insertions(+), 37 deletions(-) create mode 100644 Dappi.HeadlessCms/Exceptions/PropertyNotFoundException.cs rename Dappi.HeadlessCms/Extensions/{IEnumerableExtensions.cs => EnumerableExtensions.cs} (87%) diff --git a/Dappi.HeadlessCms/Exceptions/PropertyNotFoundException.cs b/Dappi.HeadlessCms/Exceptions/PropertyNotFoundException.cs new file mode 100644 index 00000000..7882e769 --- /dev/null +++ b/Dappi.HeadlessCms/Exceptions/PropertyNotFoundException.cs @@ -0,0 +1,18 @@ +namespace Dappi.HeadlessCms.Exceptions +{ + public class PropertyNotFoundException : Exception + { + public Type? Resource { get; set; } + public string? PropertyName { get; set; } + + public PropertyNotFoundException(string message) : base(message) + { + } + + public PropertyNotFoundException(string message, Type resource, string propertyName) : base(message) + { + Resource = resource; + PropertyName = propertyName; + } + } +} \ No newline at end of file diff --git a/Dappi.HeadlessCms/Extensions/IEnumerableExtensions.cs b/Dappi.HeadlessCms/Extensions/EnumerableExtensions.cs similarity index 87% rename from Dappi.HeadlessCms/Extensions/IEnumerableExtensions.cs rename to Dappi.HeadlessCms/Extensions/EnumerableExtensions.cs index f24bd0d1..a14a839c 100644 --- a/Dappi.HeadlessCms/Extensions/IEnumerableExtensions.cs +++ b/Dappi.HeadlessCms/Extensions/EnumerableExtensions.cs @@ -1,9 +1,10 @@ using System.Dynamic; using System.Reflection; +using Dappi.HeadlessCms.Exceptions; namespace Dappi.HeadlessCms.Extensions { - public static class IEnumerableExtensions + public static class EnumerableExtensions { public static IEnumerable ShapeData(this IEnumerable source, string? fields) { @@ -27,7 +28,7 @@ public static IEnumerable ShapeData(this IEnumerable(this TSource source, string? fi | BindingFlags.Instance); if (propertyInfo is null) { - throw new Exception($"Property {propName} not found in {typeof(TSource).FullName}"); + throw new PropertyNotFoundException($"Property {propName} not found in {typeof(TSource).FullName}"); } var propertyValue = propertyInfo.GetValue(source); diff --git a/Dappi.SourceGenerator/CrudGenerator.cs b/Dappi.SourceGenerator/CrudGenerator.cs index fe016509..443aea7e 100644 --- a/Dappi.SourceGenerator/CrudGenerator.cs +++ b/Dappi.SourceGenerator/CrudGenerator.cs @@ -44,6 +44,7 @@ protected override void Execute(SourceProductionContext context, using Dappi.HeadlessCms.Models; using Dappi.HeadlessCms.Interfaces; using Dappi.HeadlessCms.Extensions; +using Dappi.HeadlessCms.Exceptions; using {item.ModelNamespace}; using {item.RootNamespace}.Filtering; using {item.RootNamespace}.HelperDtos; @@ -73,55 +74,69 @@ public partial class {item.ClassName}Controller( {PropagateDappiAuthorizationTags(item.AuthorizeAttributes, AuthorizeMethods.Get)} public async Task Get{item.ClassName.Pluralize()}([FromQuery] {item.ClassName}Filter? filter, [FromQuery] string? fields = null) {{ - var query = dbContext.{item.ClassName.Pluralize()}.AsNoTracking().AsQueryable(); - - query = query{includesCode}; - - if (filter != null) + try {{ - query = LinqExtensions.ApplyFiltering(query, filter); - }} + var query = dbContext.{item.ClassName.Pluralize()}.AsNoTracking().AsQueryable(); + + query = query{includesCode}; - if (!string.IsNullOrEmpty(filter.SortBy)) - {{ - query = LinqExtensions.ApplySorting(query, filter.SortBy, filter.SortDirection); - }} + if (filter != null) + {{ + query = LinqExtensions.ApplyFiltering(query, filter); + }} - var total = await query.CountAsync(); - var data = await query - .Skip(filter.Offset) - .Take(filter.Limit) - .ToListAsync(); + if (!string.IsNullOrEmpty(filter.SortBy)) + {{ + query = LinqExtensions.ApplySorting(query, filter.SortBy, filter.SortDirection); + }} - var listDto = new ListResponseDTO - {{ - Data = data.ShapeData(fields), - Limit = filter.Limit, - Offset = filter.Offset, - Total = total - }}; + var total = await query.CountAsync(); + var data = await query + .Skip(filter.Offset) + .Take(filter.Limit) + .ToListAsync(); - return Ok(listDto); + var listDto = new ListResponseDTO + {{ + Data = data.ShapeData(fields), + Limit = filter.Limit, + Offset = filter.Offset, + Total = total + }}; + + return Ok(listDto); + }} + catch(PropertyNotFoundException ex) + {{ + return BadRequest(new {{message = ex.Message}}); + }} }} [HttpGet(""{{id}}"")] {PropagateDappiAuthorizationTags(item.AuthorizeAttributes, AuthorizeMethods.Get)} public async Task Get{item.ClassName}(Guid id, [FromQuery] string? fields = null) {{ - if (id == Guid.Empty) - return BadRequest(); + try + {{ + if (id == Guid.Empty) + return BadRequest(); - var query = dbContext.{item.ClassName.Pluralize()}.AsNoTracking().AsQueryable(); - - query = query{includesCode}; + var query = dbContext.{item.ClassName.Pluralize()}.AsNoTracking().AsQueryable(); + + query = query{includesCode}; - var result = await query - .FirstOrDefaultAsync(p => p.Id == id); + var result = await query + .FirstOrDefaultAsync(p => p.Id == id); - if (result is null) - return NotFound(); + if (result is null) + return NotFound(); - return Ok(result.ShapeObject(fields)); + return Ok(result.ShapeObject(fields)); + }} + catch(PropertyNotFoundException ex) + {{ + return BadRequest(new {{message = ex.Message}}); + }} }} [HttpPost] From dfd18d58b3c3175fb026faf703af1f19030f83fc Mon Sep 17 00:00:00 2001 From: David Pavlovski Date: Thu, 27 Nov 2025 13:45:28 +0100 Subject: [PATCH 4/7] test: data shaping test, fix integration tests --- Dappi.HeadlessCms.Tests/Auth/AuthHandler.cs | 14 ++ Dappi.HeadlessCms.Tests/Auth/AuthTestModel.cs | 9 + .../Controllers/ModelsControllerTests.cs | 65 +++++ .../Dappi.HeadlessCms.Tests.csproj | 1 + .../DataShaping/DataShapingTests.cs | 235 ++++++++++++++++++ .../IntegrationWebAppFactory.cs | 2 +- .../response.verified.txt | 5 +- Dappi.TestEnv/appsettings.json | 21 +- 8 files changed, 344 insertions(+), 8 deletions(-) create mode 100644 Dappi.HeadlessCms.Tests/Auth/AuthHandler.cs create mode 100644 Dappi.HeadlessCms.Tests/Auth/AuthTestModel.cs create mode 100644 Dappi.HeadlessCms.Tests/DataShaping/DataShapingTests.cs diff --git a/Dappi.HeadlessCms.Tests/Auth/AuthHandler.cs b/Dappi.HeadlessCms.Tests/Auth/AuthHandler.cs new file mode 100644 index 00000000..6e8a936a --- /dev/null +++ b/Dappi.HeadlessCms.Tests/Auth/AuthHandler.cs @@ -0,0 +1,14 @@ +using System.Net.Http.Json; + +namespace Dappi.HeadlessCms.Tests.Auth +{ + public static class AuthHandler + { + public static async Task Authorize(this HttpClient client) + { + var auth = await client.PostAsJsonAsync("/api/Auth/login" , new {Username = "Admin", Password = "Dappi@123"}); + auth.EnsureSuccessStatusCode(); + return await auth.Content.ReadFromJsonAsync(); + } + } +} \ No newline at end of file diff --git a/Dappi.HeadlessCms.Tests/Auth/AuthTestModel.cs b/Dappi.HeadlessCms.Tests/Auth/AuthTestModel.cs new file mode 100644 index 00000000..15200e04 --- /dev/null +++ b/Dappi.HeadlessCms.Tests/Auth/AuthTestModel.cs @@ -0,0 +1,9 @@ +namespace Dappi.HeadlessCms.Tests.Auth +{ + public class AuthTestModel + { + public string Token { get; set; } + public string Username { get; set; } + public List Roles { get; set; } + } +} \ No newline at end of file diff --git a/Dappi.HeadlessCms.Tests/Controllers/ModelsControllerTests.cs b/Dappi.HeadlessCms.Tests/Controllers/ModelsControllerTests.cs index 85f7d356..85d557b3 100644 --- a/Dappi.HeadlessCms.Tests/Controllers/ModelsControllerTests.cs +++ b/Dappi.HeadlessCms.Tests/Controllers/ModelsControllerTests.cs @@ -1,6 +1,7 @@ using System.Net.Http.Json; using System.Text.RegularExpressions; using Dappi.HeadlessCms.Models; +using Dappi.HeadlessCms.Tests.Auth; using Dappi.HeadlessCms.Tests.TestData; namespace Dappi.HeadlessCms.Tests.Controllers @@ -31,6 +32,7 @@ public class TestDbContext(DbContextOptions options) : DappiDbContext(options) public ModelsControllerTests(IntegrationWebAppFactory factory) : base(factory) { _client = factory.CreateClient(); + _entitiesPath = "Entities"; _dbContextPath = "Data"; _verifySettings = new VerifySettings(); @@ -53,6 +55,9 @@ public ModelsControllerTests(IntegrationWebAppFactory factory) : base(factory) [Fact] public async Task CreateModel_Should_Return_BadRequest_If_Model_Name_Is_Empty() { + var auth = await _client.Authorize(); + _client.DefaultRequestHeaders.Add("Authorization", $"Bearer {auth?.Token}"); + var request = new ModelRequest { ModelName = string.Empty, IsAuditableEntity = false }; var res = await _client.PostAsJsonAsync(_baseUrl, request); @@ -66,6 +71,9 @@ public async Task CreateModel_Should_Return_BadRequest_If_Model_Name_Is_Empty() [ClassData(typeof(InvalidPropertyTypesAndClassNames))] public async Task CreateModel_Should_Return_BadRequest_If_Model_Name_Is_Invalid(string modelName) { + var auth = await _client.Authorize(); + _client.DefaultRequestHeaders.Add("Authorization", $"Bearer {auth?.Token}"); + var request = new ModelRequest { ModelName = modelName, IsAuditableEntity = false }; var res = await _client.PostAsJsonAsync(_baseUrl, request); @@ -78,6 +86,9 @@ public async Task CreateModel_Should_Return_BadRequest_If_Model_Name_Is_Invalid( [Fact] public async Task CreateModel_Should_Create_Model_File() { + var auth = await _client.Authorize(); + _client.DefaultRequestHeaders.Add("Authorization", $"Bearer {auth?.Token}"); + var request = new ModelRequest { ModelName = "Product", IsAuditableEntity = false }; var res = await _client.PostAsJsonAsync(_baseUrl, request); var filePath = Path.Combine(_entitiesPath, $"{request.ModelName}.cs"); @@ -95,6 +106,9 @@ public async Task CreateModel_Should_Create_Model_File() [Fact] public async Task CreateModel_Should_Return_BadRequest_If_Model_Name_Is_Already_Taken() { + var auth = await _client.Authorize(); + _client.DefaultRequestHeaders.Add("Authorization", $"Bearer {auth?.Token}"); + var request = new ModelRequest { ModelName = "DuplicateModel", IsAuditableEntity = false }; await _client.PostAsJsonAsync(_baseUrl, request); var res = await _client.PostAsJsonAsync(_baseUrl, request); @@ -107,6 +121,9 @@ public async Task CreateModel_Should_Return_BadRequest_If_Model_Name_Is_Already_ [Fact] public async Task CreateModel_Should_Create_Model_File_With_Auditable_Props() { + var auth = await _client.Authorize(); + _client.DefaultRequestHeaders.Add("Authorization", $"Bearer {auth?.Token}"); + var request = new ModelRequest { ModelName = "InventoryItem", IsAuditableEntity = true }; var res = await _client.PostAsJsonAsync(_baseUrl, request); var filePath = Path.Combine(_entitiesPath, $"{request.ModelName}.cs"); @@ -124,6 +141,9 @@ public async Task CreateModel_Should_Create_Model_File_With_Auditable_Props() [Fact] public async Task GetAllModels_Should_Return_All_Models() { + var auth = await _client.Authorize(); + _client.DefaultRequestHeaders.Add("Authorization", $"Bearer {auth?.Token}"); + await _client.PostAsJsonAsync(_baseUrl, new ModelRequest { ModelName = "TestModel1", IsAuditableEntity = false }); await _client.PostAsJsonAsync(_baseUrl, @@ -141,6 +161,9 @@ await _client.PostAsJsonAsync(_baseUrl, [Fact] public async Task AddField_Should_Return_BadRequest_If_Field_Name_Is_Empty() { + var auth = await _client.Authorize(); + _client.DefaultRequestHeaders.Add("Authorization", $"Bearer {auth?.Token}"); + var request = new FieldRequest { FieldName = string.Empty, @@ -160,6 +183,9 @@ public async Task AddField_Should_Return_BadRequest_If_Field_Name_Is_Empty() [ClassData(typeof(InvalidPropertyTypesAndClassNames))] public async Task AddField_Should_Return_BadRequest_If_Field_Name_Is_Invalid(string fieldName) { + var auth = await _client.Authorize(); + _client.DefaultRequestHeaders.Add("Authorization", $"Bearer {auth?.Token}"); + var request = new FieldRequest { FieldName = fieldName, @@ -179,6 +205,9 @@ public async Task AddField_Should_Return_BadRequest_If_Field_Name_Is_Invalid(str [Fact] public async Task AddField_Should_Return_BadRequest_If_Field_Name_Is_Same_As_Model() { + var auth = await _client.Authorize(); + _client.DefaultRequestHeaders.Add("Authorization", $"Bearer {auth?.Token}"); + var request = new FieldRequest { FieldName = "Product", @@ -199,6 +228,9 @@ public async Task AddField_Should_Return_BadRequest_If_Field_Name_Is_Same_As_Mod [Fact] public async Task AddField_Should_Return_NotFound_If_Model_Does_Not_Exist() { + var auth = await _client.Authorize(); + _client.DefaultRequestHeaders.Add("Authorization", $"Bearer {auth?.Token}"); + var request = new FieldRequest { FieldName = "Product", @@ -219,6 +251,9 @@ public async Task AddField_Should_Return_NotFound_If_Model_Does_Not_Exist() [Theory] public async Task AddField_Should_Add_Required_Field_To_Model(string fieldName, string fieldType) { + var auth = await _client.Authorize(); + _client.DefaultRequestHeaders.Add("Authorization", $"Bearer {auth?.Token}"); + var model = new ModelRequest { ModelName = "ProductTwo", IsAuditableEntity = false }; var request = new FieldRequest { @@ -245,6 +280,9 @@ public async Task AddField_Should_Add_Required_Field_To_Model(string fieldName, [Theory] public async Task AddField_Should_Add_Optional_Field_To_Model(string fieldName, string fieldType) { + var auth = await _client.Authorize(); + _client.DefaultRequestHeaders.Add("Authorization", $"Bearer {auth?.Token}"); + var model = new ModelRequest { ModelName = "ProductThree", IsAuditableEntity = false }; var request = new FieldRequest { @@ -270,6 +308,9 @@ public async Task AddField_Should_Add_Optional_Field_To_Model(string fieldName, [Fact] public async Task AddField_Should_Add_OneToOne_Relation_To_Models() { + var auth = await _client.Authorize(); + _client.DefaultRequestHeaders.Add("Authorization", $"Bearer {auth?.Token}"); + var type1 = "TestClassOne"; var type2 = "TestClassTwo"; var request = new FieldRequest @@ -299,6 +340,9 @@ public async Task AddField_Should_Add_OneToOne_Relation_To_Models() [Fact] public async Task AddField_Should_Add_OneToMany_Relation_To_Models() { + var auth = await _client.Authorize(); + _client.DefaultRequestHeaders.Add("Authorization", $"Bearer {auth?.Token}"); + var type1 = "TestClassThree"; var type2 = "TestClassFour"; var request = new FieldRequest @@ -329,6 +373,9 @@ public async Task AddField_Should_Add_OneToMany_Relation_To_Models() [Fact] public async Task AddField_Should_Add_ManyToOne_Relation_To_Models() { + var auth = await _client.Authorize(); + _client.DefaultRequestHeaders.Add("Authorization", $"Bearer {auth?.Token}"); + var type1 = "TestClassFive"; var type2 = "TestClassSix"; var request = new FieldRequest @@ -359,6 +406,9 @@ public async Task AddField_Should_Add_ManyToOne_Relation_To_Models() [Fact] public async Task AddField_Should_Add_ManyToMany_Relation_To_Models() { + var auth = await _client.Authorize(); + _client.DefaultRequestHeaders.Add("Authorization", $"Bearer {auth?.Token}"); + var type1 = "TestClassSeven"; var type2 = "TestClassEight"; var request = new FieldRequest @@ -389,6 +439,9 @@ public async Task AddField_Should_Add_ManyToMany_Relation_To_Models() [Fact] public async Task DeleteModel_Should_Return_NotFound_If_Model_Does_Not_Exist() { + var auth = await _client.Authorize(); + _client.DefaultRequestHeaders.Add("Authorization", $"Bearer {auth?.Token}"); + var res = await _client.DeleteAsync($"{_baseUrl}/NotExistingClass"); _verifySettings.UseDirectory($"{_snapshotPath}/{nameof(DeleteModel_Should_Return_NotFound_If_Model_Does_Not_Exist)}"); await Verify(res, _verifySettings).UseFileName("response"); @@ -397,6 +450,9 @@ public async Task DeleteModel_Should_Return_NotFound_If_Model_Does_Not_Exist() [Fact] public async Task DeleteModel_Should_Return_BadRequest_If_ModelName_Is_Empty() { + var auth = await _client.Authorize(); + _client.DefaultRequestHeaders.Add("Authorization", $"Bearer {auth?.Token}"); + var res = await _client.DeleteAsync($"{_baseUrl}/{string.Empty}"); _verifySettings.UseDirectory($"{_snapshotPath}/{nameof(DeleteModel_Should_Return_BadRequest_If_ModelName_Is_Empty)}"); await Verify(res, _verifySettings).UseFileName("response"); @@ -405,6 +461,9 @@ public async Task DeleteModel_Should_Return_BadRequest_If_ModelName_Is_Empty() [Fact] public async Task DeleteModel_Should_Delete_Model_File() { + var auth = await _client.Authorize(); + _client.DefaultRequestHeaders.Add("Authorization", $"Bearer {auth?.Token}"); + var modelName = "TestDeleteModel"; await _client.PostAsJsonAsync(_baseUrl, new ModelRequest { ModelName = modelName, IsAuditableEntity = false }); var res = await _client.DeleteAsync($"{_baseUrl}/{modelName}"); @@ -421,6 +480,9 @@ public async Task DeleteModel_Should_Delete_Model_File() [Fact] public async Task DeleteModel_Should_Delete_Relations_And_References() { + var auth = await _client.Authorize(); + _client.DefaultRequestHeaders.Add("Authorization", $"Bearer {auth?.Token}"); + const string user = "User"; const string post = "Post"; var request = new FieldRequest @@ -454,6 +516,9 @@ public async Task DeleteModel_Should_Delete_Relations_And_References() [Fact] public async Task Other_Relations_Should_Not_Be_Deleted() { + var auth = await _client.Authorize(); + _client.DefaultRequestHeaders.Add("Authorization", $"Bearer {auth?.Token}"); + const string user = "User"; const string post = "Post"; const string comment = "Comment"; diff --git a/Dappi.HeadlessCms.Tests/Dappi.HeadlessCms.Tests.csproj b/Dappi.HeadlessCms.Tests/Dappi.HeadlessCms.Tests.csproj index e864a565..a1b2fa0a 100644 --- a/Dappi.HeadlessCms.Tests/Dappi.HeadlessCms.Tests.csproj +++ b/Dappi.HeadlessCms.Tests/Dappi.HeadlessCms.Tests.csproj @@ -8,6 +8,7 @@ + diff --git a/Dappi.HeadlessCms.Tests/DataShaping/DataShapingTests.cs b/Dappi.HeadlessCms.Tests/DataShaping/DataShapingTests.cs new file mode 100644 index 00000000..ed3fe684 --- /dev/null +++ b/Dappi.HeadlessCms.Tests/DataShaping/DataShapingTests.cs @@ -0,0 +1,235 @@ +using Bogus; +using Dappi.HeadlessCms.Exceptions; +using Dappi.HeadlessCms.Extensions; + +namespace Dappi.HeadlessCms.Tests.DataShaping +{ + internal class DataShapingDummyModel + { + public Guid Id { get; set; } + public string Name { get; set; } + public DateTime CreatedAt { get; set; } + public bool IsDeleted { get; set; } + private string PrivateField { get; set; } = "Private"; + protected string ProtectedField { get; set; } = "Protected"; + internal string InternalField { get; set; } = "Internal"; + + public static Faker Faker { get; } = new Faker() + .RuleFor(x => x.Id, f => Guid.NewGuid()) + .RuleFor(x => x.Name, f => f.Name.FirstName()) + .RuleFor(x => x.CreatedAt, f => f.Date.Past()) + .RuleFor(x => x.IsDeleted, f => f.Random.Bool()); + } + + public class DataShapingTests + { + private readonly DataShapingDummyModel _dummyModel = DataShapingDummyModel.Faker.Generate(); + private readonly List _dummyModels = DataShapingDummyModel.Faker.Generate(10); + + [Fact] + public void ShapeObject_Should_Throw_If_Property_Does_Not_Exist() + { + Assert.Throws(() => _dummyModel.ShapeObject("NonExistingProperty")); + } + + [Fact] + public void ShapeObject_Should_Throw_For_Private_Properties() + { + Assert.Throws(() => _dummyModel.ShapeObject("PrivateField")); + } + + [Fact] + public void ShapeObject_Should_Throw_For_Protected_Properties() + { + Assert.Throws(() => _dummyModel.ShapeObject("ProtectedField")); + } + + [Fact] + public void ShapeObject_Should_Throw_For_Internal_Properties() + { + Assert.Throws(() => _dummyModel.ShapeObject("InternalField")); + } + + [Fact] + public void ShapeObject_Should_Return_All_Properties_If_Fields_Param_Is_Null() + { + IDictionary expandoObject = _dummyModel.ShapeObject(null); + + Assert.Equal(4, expandoObject.Count); + Assert.Contains("Id",expandoObject.Keys); + Assert.Contains("Name",expandoObject.Keys); + Assert.Contains("IsDeleted",expandoObject.Keys); + Assert.Contains("CreatedAt",expandoObject.Keys); + } + + [Fact] + public void ShapeObject_Should_Return_All_Properties_If_Fields_Param_Is_EmptyString() + { + IDictionary expandoObject = _dummyModel.ShapeObject(string.Empty); + + Assert.Equal(4, expandoObject.Count); + Assert.Contains("Id",expandoObject.Keys); + Assert.Contains("Name",expandoObject.Keys); + Assert.Contains("IsDeleted",expandoObject.Keys); + Assert.Contains("CreatedAt",expandoObject.Keys); + } + + [Fact] + public void ShapeObject_Should_Return_Only_Selected_Properties() + { + const string fields = "Id,Name"; + IDictionary expandoObject = _dummyModel.ShapeObject(fields); + + Assert.Equal(fields.Split(',').Length, expandoObject.Count); + Assert.Contains("Id",expandoObject.Keys); + Assert.Contains("Name",expandoObject.Keys); + } + + [Fact] + public void ShapeObject_Should_Ignore_Case_Selected_Properties() + { + const string fields = "Id,nAMe"; + IDictionary expandoObject = _dummyModel.ShapeObject(fields); + + Assert.Equal(fields.Split(',').Length, expandoObject.Count); + Assert.Contains("Id",expandoObject.Keys); + Assert.Contains("Name",expandoObject.Keys); + } + + [Fact] + public void ShapeObject_Should_Return_Correct_Values_For_All_Properties() + { + IDictionary expandoObject = _dummyModel.ShapeObject(null); + + Assert.Equal(4,expandoObject.Count); + Assert.Equal(_dummyModel.Id, expandoObject["Id"]); + Assert.Equal(_dummyModel.Name, expandoObject["Name"]); + Assert.Equal(_dummyModel.IsDeleted, expandoObject["IsDeleted"]); + Assert.Equal(_dummyModel.CreatedAt, expandoObject["CreatedAt"]); + } + + [Fact] + public void ShapeObject_Should_Return_Correct_Values_For_Selected_Properties() + { + const string fields = "Id,Name,IsDeleted"; + IDictionary expandoObject = _dummyModel.ShapeObject("id,name,isDeleted"); + + Assert.Equal(fields.Split(',').Length, expandoObject.Count); + Assert.Equal(_dummyModel.Id, expandoObject["Id"]); + Assert.Equal(_dummyModel.Name, expandoObject["Name"]); + Assert.Equal(_dummyModel.IsDeleted, expandoObject["IsDeleted"]); + } + + [Fact] + public void ShapeData_Should_Throw_If_Property_Does_Not_Exist() + { + Assert.Throws(() => _dummyModels.ShapeData("NonExistingProperty")); + } + + [Fact] + public void ShapeData_Should_Throw_For_Private_Properties() + { + Assert.Throws(() => _dummyModels.ShapeData("PrivateField")); + } + + [Fact] + public void ShapeData_Should_Throw_For_Protected_Properties() + { + Assert.Throws(() => _dummyModels.ShapeData("ProtectedField")); + } + + [Fact] + public void ShapeData_Should_Throw_For_Internal_Properties() + { + Assert.Throws(() => _dummyModels.ShapeData("InternalField")); + } + + [Fact] + public void ShapeData_Should_Return_All_Properties_If_Fields_Param_Is_Null() + { + IEnumerable> expandoObjects = _dummyModels.ShapeData(null); + + foreach (var expandoObject in expandoObjects) + { + Assert.Equal(4, expandoObject.Count); + Assert.Contains("Id",expandoObject.Keys); + Assert.Contains("Name",expandoObject.Keys); + Assert.Contains("IsDeleted",expandoObject.Keys); + Assert.Contains("CreatedAt",expandoObject.Keys); + } + } + + [Fact] + public void ShapeData_Should_Return_All_Properties_If_Fields_Param_Is_EmptyString() + { + IEnumerable> expandoObjects = _dummyModels.ShapeData(string.Empty); + + foreach (var expandoObject in expandoObjects) + { + Assert.Equal(4, expandoObject.Count); + Assert.Contains("Id",expandoObject.Keys); + Assert.Contains("Name",expandoObject.Keys); + Assert.Contains("IsDeleted",expandoObject.Keys); + Assert.Contains("CreatedAt",expandoObject.Keys); + } + } + + [Fact] + public void ShapeData_Should_Return_Selected_Properties() + { + const string fields = "Id,Name"; + IEnumerable> expandoObjects = _dummyModels.ShapeData(fields); + + foreach (var expandoObject in expandoObjects) + { + Assert.Equal(fields.Split(',').Length, expandoObject.Count); + Assert.Contains("Id",expandoObject.Keys); + Assert.Contains("Name",expandoObject.Keys); + } + } + + [Fact] + public void ShapeData_Should_Ignore_Case() + { + const string fields = "iD,nAMe"; + IEnumerable> expandoObjects = _dummyModels.ShapeData(fields); + + foreach (var expandoObject in expandoObjects) + { + Assert.Equal(fields.Split(',').Length, expandoObject.Count); + Assert.Contains("Id",expandoObject.Keys); + Assert.Contains("Name",expandoObject.Keys); + } + } + + [Fact] + public void ShapeData_Should_Return_Correct_Values_For_All_Properties() + { + IEnumerable> expandoObjects = _dummyModels.ShapeData(null).ToList(); + for (var i = 0; i < _dummyModels.Count; i++) + { + var expandoObject = expandoObjects.ElementAt(i); + Assert.Equal(4,expandoObject.Count); + Assert.Equal(_dummyModels[i].Id, expandoObject["Id"]); + Assert.Equal(_dummyModels[i].Name, expandoObject["Name"]); + Assert.Equal(_dummyModels[i].IsDeleted, expandoObject["IsDeleted"]); + Assert.Equal(_dummyModels[i].CreatedAt, expandoObject["CreatedAt"]); + } + } + + [Fact] + public void ShapeData_Should_Return_Correct_Values_For_Selected_Properties() + { + const string fields = "Id,Name,IsDeleted"; + IEnumerable> expandoObjects = _dummyModels.ShapeData(fields).ToList(); + for (var i = 0; i < _dummyModels.Count; i++) + { + var expandoObject = expandoObjects.ElementAt(i); + Assert.Equal(fields.Split(',').Length,expandoObject.Count); + Assert.Equal(_dummyModels[i].Id, expandoObject["Id"]); + Assert.Equal(_dummyModels[i].Name, expandoObject["Name"]); + Assert.Equal(_dummyModels[i].IsDeleted, expandoObject["IsDeleted"]); + } + } + } +} \ No newline at end of file diff --git a/Dappi.HeadlessCms.Tests/IntegrationWebAppFactory.cs b/Dappi.HeadlessCms.Tests/IntegrationWebAppFactory.cs index 09c88e23..8825e57c 100644 --- a/Dappi.HeadlessCms.Tests/IntegrationWebAppFactory.cs +++ b/Dappi.HeadlessCms.Tests/IntegrationWebAppFactory.cs @@ -24,7 +24,7 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) var existingDbContext = services.SingleOrDefault(x => x.ServiceType == typeof(TestDbContext)); if (existingDbContext != null) services.Remove(existingDbContext); - + services.AddDbContext(options => { options.UseNpgsql(_dbContainer.GetConnectionString(), b => b.MigrationsAssembly("Dappi.TestEnv")); diff --git a/Dappi.HeadlessCms.Tests/snapshots/ModelsControllerTests/DeleteModel_Should_Return_BadRequest_If_ModelName_Is_Empty/response.verified.txt b/Dappi.HeadlessCms.Tests/snapshots/ModelsControllerTests/DeleteModel_Should_Return_BadRequest_If_ModelName_Is_Empty/response.verified.txt index 791ce57a..e664cb9a 100644 --- a/Dappi.HeadlessCms.Tests/snapshots/ModelsControllerTests/DeleteModel_Should_Return_BadRequest_If_ModelName_Is_Empty/response.verified.txt +++ b/Dappi.HeadlessCms.Tests/snapshots/ModelsControllerTests/DeleteModel_Should_Return_BadRequest_If_ModelName_Is_Empty/response.verified.txt @@ -4,6 +4,9 @@ }, Request: { Method: DELETE, - Uri: http://localhost/api/models/ + Uri: http://localhost/api/models/, + Headers: { + Authorization: {Scrubbed} + } } } \ No newline at end of file diff --git a/Dappi.TestEnv/appsettings.json b/Dappi.TestEnv/appsettings.json index 96f9a991..fc25366f 100644 --- a/Dappi.TestEnv/appsettings.json +++ b/Dappi.TestEnv/appsettings.json @@ -1,12 +1,21 @@ { - "JwtSettings": { - "SecretKey": "your_super_secret_key_with_at_least_32_characters", - "Issuer": "dappi.net", - "Audience": "dappi.net.audience", - "ExpirationHours": 1 + "Authentication": { + "Dappi": { + "SecretKey": "your_super_secret_key_with_at_least_32_characters", + "Issuer": "dappi.net", + "Audience": "dappi.net.audience", + "ExpirationHours": 1 + }, + "Custom": { + // Add needed keys for your custom JWT validation + "SecretKey": "your_super_secret_key_with_at_least_32_characters", + "Issuer": "custom.net", + "Audience": "custom.net.audience", + "ExpirationHours": 1 + } }, "Dappi": { -"PostgresConnection": "Host=localhost;Port=5432;Database=MyCompany.MyProject.TestDB;Username=postgres;Password=admin", + "PostgresConnection": "Host=localhost;Port=5432;Database=MyCompany.MyProject.DB;Username=postgres;Password=admin", "FrontendUrl": "http://localhost:4200" }, "Logging": { From bcd8e06f9bb7f6b7e4515278f713b82b07520c6c Mon Sep 17 00:00:00 2001 From: David Pavlovski Date: Fri, 28 Nov 2025 14:44:37 +0100 Subject: [PATCH 5/7] refactor data shaping logic --- .../DataShaping/DataShapingTests.cs | 111 ++++++++++++------ .../IntegrationWebAppFactory.cs | 1 - .../Extensions/EnumerableExtensions.cs | 52 -------- .../Extensions/ObjectExtensions.cs | 47 -------- Dappi.HeadlessCms/Interfaces/IDataShaper.cs | 9 ++ Dappi.HeadlessCms/ServiceExtensions.cs | 2 +- Dappi.HeadlessCms/Services/DataShaper.cs | 65 ++++++++++ Dappi.SourceGenerator/CrudGenerator.cs | 7 +- 8 files changed, 156 insertions(+), 138 deletions(-) delete mode 100644 Dappi.HeadlessCms/Extensions/EnumerableExtensions.cs delete mode 100644 Dappi.HeadlessCms/Extensions/ObjectExtensions.cs create mode 100644 Dappi.HeadlessCms/Interfaces/IDataShaper.cs create mode 100644 Dappi.HeadlessCms/Services/DataShaper.cs diff --git a/Dappi.HeadlessCms.Tests/DataShaping/DataShapingTests.cs b/Dappi.HeadlessCms.Tests/DataShaping/DataShapingTests.cs index ed3fe684..2ed0d044 100644 --- a/Dappi.HeadlessCms.Tests/DataShaping/DataShapingTests.cs +++ b/Dappi.HeadlessCms.Tests/DataShaping/DataShapingTests.cs @@ -1,6 +1,6 @@ using Bogus; using Dappi.HeadlessCms.Exceptions; -using Dappi.HeadlessCms.Extensions; +using Dappi.HeadlessCms.Services; namespace Dappi.HeadlessCms.Tests.DataShaping { @@ -13,7 +13,6 @@ internal class DataShapingDummyModel private string PrivateField { get; set; } = "Private"; protected string ProtectedField { get; set; } = "Protected"; internal string InternalField { get; set; } = "Internal"; - public static Faker Faker { get; } = new Faker() .RuleFor(x => x.Id, f => Guid.NewGuid()) .RuleFor(x => x.Name, f => f.Name.FirstName()) @@ -29,31 +28,36 @@ public class DataShapingTests [Fact] public void ShapeObject_Should_Throw_If_Property_Does_Not_Exist() { - Assert.Throws(() => _dummyModel.ShapeObject("NonExistingProperty")); + var shaper = new DataShaper(); + Assert.Throws(() => shaper.ShapeObject(_dummyModels,"NonExistingProperty")); } [Fact] public void ShapeObject_Should_Throw_For_Private_Properties() { - Assert.Throws(() => _dummyModel.ShapeObject("PrivateField")); + var shaper = new DataShaper(); + Assert.Throws(() => shaper.ShapeObject(_dummyModel,"PrivateField")); } [Fact] public void ShapeObject_Should_Throw_For_Protected_Properties() { - Assert.Throws(() => _dummyModel.ShapeObject("ProtectedField")); + var shaper = new DataShaper(); + Assert.Throws(() => shaper.ShapeObject(_dummyModel,"ProtectedField")); } [Fact] public void ShapeObject_Should_Throw_For_Internal_Properties() { - Assert.Throws(() => _dummyModel.ShapeObject("InternalField")); + var shaper = new DataShaper(); + Assert.Throws(() => shaper.ShapeObject(_dummyModel,"InternalField")); } [Fact] public void ShapeObject_Should_Return_All_Properties_If_Fields_Param_Is_Null() { - IDictionary expandoObject = _dummyModel.ShapeObject(null); + var shaper = new DataShaper(); + IDictionary expandoObject = shaper.ShapeObject( _dummyModel,null); Assert.Equal(4, expandoObject.Count); Assert.Contains("Id",expandoObject.Keys); @@ -65,7 +69,8 @@ public void ShapeObject_Should_Return_All_Properties_If_Fields_Param_Is_Null() [Fact] public void ShapeObject_Should_Return_All_Properties_If_Fields_Param_Is_EmptyString() { - IDictionary expandoObject = _dummyModel.ShapeObject(string.Empty); + var shaper = new DataShaper(); + IDictionary expandoObject = shaper.ShapeObject(_dummyModel,string.Empty); Assert.Equal(4, expandoObject.Count); Assert.Contains("Id",expandoObject.Keys); @@ -77,8 +82,9 @@ public void ShapeObject_Should_Return_All_Properties_If_Fields_Param_Is_EmptyStr [Fact] public void ShapeObject_Should_Return_Only_Selected_Properties() { + var shaper = new DataShaper(); const string fields = "Id,Name"; - IDictionary expandoObject = _dummyModel.ShapeObject(fields); + IDictionary expandoObject = shaper.ShapeObject(_dummyModel,fields); Assert.Equal(fields.Split(',').Length, expandoObject.Count); Assert.Contains("Id",expandoObject.Keys); @@ -88,8 +94,9 @@ public void ShapeObject_Should_Return_Only_Selected_Properties() [Fact] public void ShapeObject_Should_Ignore_Case_Selected_Properties() { + var shaper = new DataShaper(); const string fields = "Id,nAMe"; - IDictionary expandoObject = _dummyModel.ShapeObject(fields); + IDictionary expandoObject = shaper.ShapeObject(_dummyModel,fields); Assert.Equal(fields.Split(',').Length, expandoObject.Count); Assert.Contains("Id",expandoObject.Keys); @@ -99,7 +106,8 @@ public void ShapeObject_Should_Ignore_Case_Selected_Properties() [Fact] public void ShapeObject_Should_Return_Correct_Values_For_All_Properties() { - IDictionary expandoObject = _dummyModel.ShapeObject(null); + var shaper = new DataShaper(); + IDictionary expandoObject = shaper.ShapeObject(_dummyModel,null); Assert.Equal(4,expandoObject.Count); Assert.Equal(_dummyModel.Id, expandoObject["Id"]); @@ -111,8 +119,9 @@ public void ShapeObject_Should_Return_Correct_Values_For_All_Properties() [Fact] public void ShapeObject_Should_Return_Correct_Values_For_Selected_Properties() { + var shaper = new DataShaper(); const string fields = "Id,Name,IsDeleted"; - IDictionary expandoObject = _dummyModel.ShapeObject("id,name,isDeleted"); + IDictionary expandoObject = shaper.ShapeObject(_dummyModel,"id,name,isDeleted"); Assert.Equal(fields.Split(',').Length, expandoObject.Count); Assert.Equal(_dummyModel.Id, expandoObject["Id"]); @@ -123,31 +132,51 @@ public void ShapeObject_Should_Return_Correct_Values_For_Selected_Properties() [Fact] public void ShapeData_Should_Throw_If_Property_Does_Not_Exist() { - Assert.Throws(() => _dummyModels.ShapeData("NonExistingProperty")); + var shaper = new DataShaper(); + + foreach (var dummyModel in _dummyModels) + { + Assert.Throws(() => shaper.ShapeObject(dummyModel,"NonExistingProperty")); + } } [Fact] public void ShapeData_Should_Throw_For_Private_Properties() { - Assert.Throws(() => _dummyModels.ShapeData("PrivateField")); + var shaper = new DataShaper(); + foreach (var dummyModel in _dummyModels) + { + Assert.Throws(() => shaper.ShapeObject(dummyModel,"PrivateField")); + } } [Fact] public void ShapeData_Should_Throw_For_Protected_Properties() { - Assert.Throws(() => _dummyModels.ShapeData("ProtectedField")); + var shaper = new DataShaper(); + + foreach (var dummyModel in _dummyModels) + { + Assert.Throws(() => shaper.ShapeObject(dummyModel,"ProtectedField")); + } } [Fact] public void ShapeData_Should_Throw_For_Internal_Properties() { - Assert.Throws(() => _dummyModels.ShapeData("InternalField")); + var shaper = new DataShaper(); + foreach (var dummyModel in _dummyModels) + { + Assert.Throws(() => shaper.ShapeObject(dummyModel,"InternalField")); + } } [Fact] public void ShapeData_Should_Return_All_Properties_If_Fields_Param_Is_Null() { - IEnumerable> expandoObjects = _dummyModels.ShapeData(null); + var shaper = new DataShaper(); + IEnumerable> expandoObjects = + _dummyModels.Select(x => shaper.ShapeObject(x, null)).ToList(); foreach (var expandoObject in expandoObjects) { @@ -162,7 +191,9 @@ public void ShapeData_Should_Return_All_Properties_If_Fields_Param_Is_Null() [Fact] public void ShapeData_Should_Return_All_Properties_If_Fields_Param_Is_EmptyString() { - IEnumerable> expandoObjects = _dummyModels.ShapeData(string.Empty); + var shaper = new DataShaper(); + IEnumerable> expandoObjects = + _dummyModels.Select(x => shaper.ShapeObject(x, string.Empty)).ToList(); foreach (var expandoObject in expandoObjects) { @@ -177,8 +208,10 @@ public void ShapeData_Should_Return_All_Properties_If_Fields_Param_Is_EmptyStrin [Fact] public void ShapeData_Should_Return_Selected_Properties() { + var shaper = new DataShaper(); const string fields = "Id,Name"; - IEnumerable> expandoObjects = _dummyModels.ShapeData(fields); + IEnumerable> expandoObjects = + _dummyModels.Select(x => shaper.ShapeObject(x, fields)); foreach (var expandoObject in expandoObjects) { @@ -191,9 +224,11 @@ public void ShapeData_Should_Return_Selected_Properties() [Fact] public void ShapeData_Should_Ignore_Case() { + var shaper = new DataShaper(); const string fields = "iD,nAMe"; - IEnumerable> expandoObjects = _dummyModels.ShapeData(fields); - + IEnumerable> expandoObjects = + _dummyModels.Select(x => shaper.ShapeObject(x, fields)).ToList(); + foreach (var expandoObject in expandoObjects) { Assert.Equal(fields.Split(',').Length, expandoObject.Count); @@ -205,31 +240,39 @@ public void ShapeData_Should_Ignore_Case() [Fact] public void ShapeData_Should_Return_Correct_Values_For_All_Properties() { - IEnumerable> expandoObjects = _dummyModels.ShapeData(null).ToList(); - for (var i = 0; i < _dummyModels.Count; i++) + var shaper = new DataShaper(); + IEnumerable> expandoObjects = + _dummyModels.Select(x => shaper.ShapeObject(x, null)).ToList(); + + foreach (var expandoObject in expandoObjects) { - var expandoObject = expandoObjects.ElementAt(i); + var dummyModel = _dummyModels.FirstOrDefault(x => x.Id == Guid.Parse(expandoObject["Id"]?.ToString()!)); Assert.Equal(4,expandoObject.Count); - Assert.Equal(_dummyModels[i].Id, expandoObject["Id"]); - Assert.Equal(_dummyModels[i].Name, expandoObject["Name"]); - Assert.Equal(_dummyModels[i].IsDeleted, expandoObject["IsDeleted"]); - Assert.Equal(_dummyModels[i].CreatedAt, expandoObject["CreatedAt"]); + Assert.Equal(dummyModel!.Id, expandoObject["Id"]); + Assert.Equal(dummyModel.Name, expandoObject["Name"]); + Assert.Equal(dummyModel.IsDeleted, expandoObject["IsDeleted"]); + Assert.Equal(dummyModel.CreatedAt, expandoObject["CreatedAt"]); } + Assert.Equal(expandoObjects.Count(), _dummyModels.Count); } [Fact] public void ShapeData_Should_Return_Correct_Values_For_Selected_Properties() { + var shaper = new DataShaper(); const string fields = "Id,Name,IsDeleted"; - IEnumerable> expandoObjects = _dummyModels.ShapeData(fields).ToList(); - for (var i = 0; i < _dummyModels.Count; i++) + IEnumerable> expandoObjects = + _dummyModels.Select(x => shaper.ShapeObject(x, fields)).ToList(); + + foreach (var expandoObject in expandoObjects) { - var expandoObject = expandoObjects.ElementAt(i); + var dummyModel = _dummyModels.FirstOrDefault(x => x.Id == Guid.Parse(expandoObject["Id"]?.ToString()!)); Assert.Equal(fields.Split(',').Length,expandoObject.Count); - Assert.Equal(_dummyModels[i].Id, expandoObject["Id"]); - Assert.Equal(_dummyModels[i].Name, expandoObject["Name"]); - Assert.Equal(_dummyModels[i].IsDeleted, expandoObject["IsDeleted"]); + Assert.Equal(dummyModel!.Id, expandoObject["Id"]); + Assert.Equal(dummyModel.Name, expandoObject["Name"]); + Assert.Equal(dummyModel.IsDeleted, expandoObject["IsDeleted"]); } + Assert.Equal(expandoObjects.Count(), _dummyModels.Count); } } } \ No newline at end of file diff --git a/Dappi.HeadlessCms.Tests/IntegrationWebAppFactory.cs b/Dappi.HeadlessCms.Tests/IntegrationWebAppFactory.cs index 8825e57c..92559464 100644 --- a/Dappi.HeadlessCms.Tests/IntegrationWebAppFactory.cs +++ b/Dappi.HeadlessCms.Tests/IntegrationWebAppFactory.cs @@ -30,7 +30,6 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) options.UseNpgsql(_dbContainer.GetConnectionString(), b => b.MigrationsAssembly("Dappi.TestEnv")); }); }); - builder.ConfigureServices(services => { services.AddControllers(); diff --git a/Dappi.HeadlessCms/Extensions/EnumerableExtensions.cs b/Dappi.HeadlessCms/Extensions/EnumerableExtensions.cs deleted file mode 100644 index a14a839c..00000000 --- a/Dappi.HeadlessCms/Extensions/EnumerableExtensions.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System.Dynamic; -using System.Reflection; -using Dappi.HeadlessCms.Exceptions; - -namespace Dappi.HeadlessCms.Extensions -{ - public static class EnumerableExtensions - { - public static IEnumerable ShapeData(this IEnumerable source, string? fields) - { - ArgumentNullException.ThrowIfNull(source); - var objectList = new List(); - var propertyInfoList = new List(); - if (string.IsNullOrEmpty(fields)) - { - var propertyInfos = - typeof(TSource).GetProperties(BindingFlags.IgnoreCase | - BindingFlags.Public | - BindingFlags.Instance); - propertyInfoList.AddRange(propertyInfos); - } - else - { - foreach (var field in fields.Split(',')) - { - var propertyName = field.Trim(); - var propertyInfo = typeof(TSource).GetProperty(propertyName, - BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance); - if (propertyInfo is null) - { - throw new PropertyNotFoundException($"Property {propertyName} not found in {typeof(TSource).FullName}"); - } - - propertyInfoList.Add(propertyInfo); - } - } - - foreach (var sourceObject in source) - { - var expandoObject = new ExpandoObject(); - foreach (var propertyInfo in propertyInfoList) - { - var propertyValue = propertyInfo.GetValue(sourceObject); - ((IDictionary)expandoObject).Add(propertyInfo.Name, propertyValue); - } - objectList.Add(expandoObject); - } - - return objectList; - } - } -} \ No newline at end of file diff --git a/Dappi.HeadlessCms/Extensions/ObjectExtensions.cs b/Dappi.HeadlessCms/Extensions/ObjectExtensions.cs deleted file mode 100644 index 2396d44b..00000000 --- a/Dappi.HeadlessCms/Extensions/ObjectExtensions.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System.Dynamic; -using System.Reflection; -using Dappi.HeadlessCms.Exceptions; - -namespace Dappi.HeadlessCms.Extensions -{ - public static class ObjectExtensions - { - public static ExpandoObject ShapeObject(this TSource source, string? fields) - { - ArgumentNullException.ThrowIfNull(source); - - var dataShapedObject = new ExpandoObject(); - - if (string.IsNullOrEmpty(fields)) - { - var propertyInfos = typeof(TSource).GetProperties(BindingFlags.IgnoreCase - | BindingFlags.Public - | BindingFlags.Instance); - foreach (var propertyInfo in propertyInfos) - { - var propertyValue = propertyInfo.GetValue(source); - ((IDictionary)dataShapedObject).Add(propertyInfo.Name, propertyValue); - } - return dataShapedObject; - } - - foreach (var field in fields.Split(',')) - { - var propName = field.Trim(); - var propertyInfo = typeof(TSource).GetProperty(propName, - BindingFlags.IgnoreCase - | BindingFlags.Public - | BindingFlags.Instance); - if (propertyInfo is null) - { - throw new PropertyNotFoundException($"Property {propName} not found in {typeof(TSource).FullName}"); - } - - var propertyValue = propertyInfo.GetValue(source); - ((IDictionary)dataShapedObject).Add(propertyInfo.Name, propertyValue); - } - - return dataShapedObject; - } - } -} \ No newline at end of file diff --git a/Dappi.HeadlessCms/Interfaces/IDataShaper.cs b/Dappi.HeadlessCms/Interfaces/IDataShaper.cs new file mode 100644 index 00000000..ca982ed7 --- /dev/null +++ b/Dappi.HeadlessCms/Interfaces/IDataShaper.cs @@ -0,0 +1,9 @@ +using System.Dynamic; + +namespace Dappi.HeadlessCms.Interfaces +{ + public interface IDataShaper + { + ExpandoObject ShapeObject(TSource source, string? fields); + } +} \ No newline at end of file diff --git a/Dappi.HeadlessCms/ServiceExtensions.cs b/Dappi.HeadlessCms/ServiceExtensions.cs index fc51c697..61279ef4 100644 --- a/Dappi.HeadlessCms/ServiceExtensions.cs +++ b/Dappi.HeadlessCms/ServiceExtensions.cs @@ -56,7 +56,7 @@ public static IServiceCollection AddDappi( services.AddScoped(); services.AddScoped(); - + services.AddScoped(); services.AddDappiSwaggerGen(); services.AddControllers() diff --git a/Dappi.HeadlessCms/Services/DataShaper.cs b/Dappi.HeadlessCms/Services/DataShaper.cs new file mode 100644 index 00000000..8841c75f --- /dev/null +++ b/Dappi.HeadlessCms/Services/DataShaper.cs @@ -0,0 +1,65 @@ +using System.Dynamic; +using System.Reflection; +using Dappi.HeadlessCms.Exceptions; +using Dappi.HeadlessCms.Interfaces; + +namespace Dappi.HeadlessCms.Services +{ + public class DataShaper : IDataShaper , IDisposable + { + private const BindingFlags BindingFlags = System.Reflection.BindingFlags.IgnoreCase | + System.Reflection.BindingFlags.Public | + System.Reflection.BindingFlags.Instance; + + private readonly List _propertyInfoCache = []; + + public ExpandoObject ShapeObject(TSource source, string? fields) + { + ArgumentNullException.ThrowIfNull(source); + + var dataShapedObject = new ExpandoObject(); + + if (_propertyInfoCache.Count == 0) + { + CacheProperties(fields); + } + + foreach (var propertyInfo in _propertyInfoCache) + { + var propertyValue = propertyInfo.GetValue(source); + ((IDictionary)dataShapedObject).Add(propertyInfo.Name, propertyValue); + } + + return dataShapedObject; + } + + private void CacheProperties(string? fields) + { + if (string.IsNullOrEmpty(fields)) + { + var propertyInfos = typeof(TSource).GetProperties(BindingFlags); + _propertyInfoCache.AddRange(propertyInfos); + } + else + { + foreach (var field in fields.Split(',')) + { + var propertyName = field.Trim(); + var propertyInfo = typeof(TSource).GetProperty(propertyName, BindingFlags); + if (propertyInfo is null) + { + throw new PropertyNotFoundException( + $"Property {propertyName} not found in {typeof(TSource).FullName}"); + } + + _propertyInfoCache.Add(propertyInfo); + } + } + } + + public void Dispose() + { + _propertyInfoCache.Clear(); + } + } +} \ No newline at end of file diff --git a/Dappi.SourceGenerator/CrudGenerator.cs b/Dappi.SourceGenerator/CrudGenerator.cs index 443aea7e..f58c1e5f 100644 --- a/Dappi.SourceGenerator/CrudGenerator.cs +++ b/Dappi.SourceGenerator/CrudGenerator.cs @@ -67,7 +67,8 @@ namespace {item.RootNamespace}.Controllers; [ApiController] [Route(""api/[controller]"")] public partial class {item.ClassName}Controller( - {dbContextData.ClassName} dbContext, + {dbContextData.ClassName} dbContext, + IDataShaper shaper, IMediaUploadService uploadService) : ControllerBase {{ [HttpGet] @@ -98,7 +99,7 @@ public partial class {item.ClassName}Controller( var listDto = new ListResponseDTO {{ - Data = data.ShapeData(fields), + Data = data.Select(x => shaper.ShapeObject(x,fields)), Limit = filter.Limit, Offset = filter.Offset, Total = total @@ -131,7 +132,7 @@ public partial class {item.ClassName}Controller( if (result is null) return NotFound(); - return Ok(result.ShapeObject(fields)); + return Ok(shaper.ShapeObject(result,fields)); }} catch(PropertyNotFoundException ex) {{ From f016dcabfee07eab03265fc3ebe45c7824d174a9 Mon Sep 17 00:00:00 2001 From: David Pavlovski Date: Fri, 28 Nov 2025 15:04:07 +0100 Subject: [PATCH 6/7] refactor data shaper to cache to dictionary instead of list --- Dappi.HeadlessCms/Services/DataShaper.cs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/Dappi.HeadlessCms/Services/DataShaper.cs b/Dappi.HeadlessCms/Services/DataShaper.cs index 8841c75f..9b1fb746 100644 --- a/Dappi.HeadlessCms/Services/DataShaper.cs +++ b/Dappi.HeadlessCms/Services/DataShaper.cs @@ -5,13 +5,14 @@ namespace Dappi.HeadlessCms.Services { - public class DataShaper : IDataShaper , IDisposable + public class DataShaper : IDataShaper, IDisposable { private const BindingFlags BindingFlags = System.Reflection.BindingFlags.IgnoreCase | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance; - private readonly List _propertyInfoCache = []; + private readonly IDictionary> _propertyInfoCache = + new Dictionary>(); public ExpandoObject ShapeObject(TSource source, string? fields) { @@ -19,12 +20,12 @@ public ExpandoObject ShapeObject(TSource source, string? fields) var dataShapedObject = new ExpandoObject(); - if (_propertyInfoCache.Count == 0) + if (!_propertyInfoCache.TryGetValue(source.GetType().Name, out var value) || value.Count == 0) { CacheProperties(fields); } - foreach (var propertyInfo in _propertyInfoCache) + foreach (var propertyInfo in _propertyInfoCache[source.GetType().Name]) { var propertyValue = propertyInfo.GetValue(source); ((IDictionary)dataShapedObject).Add(propertyInfo.Name, propertyValue); @@ -38,7 +39,7 @@ private void CacheProperties(string? fields) if (string.IsNullOrEmpty(fields)) { var propertyInfos = typeof(TSource).GetProperties(BindingFlags); - _propertyInfoCache.AddRange(propertyInfos); + _propertyInfoCache.Add(typeof(TSource).Name, propertyInfos.ToList()); } else { @@ -52,7 +53,14 @@ private void CacheProperties(string? fields) $"Property {propertyName} not found in {typeof(TSource).FullName}"); } - _propertyInfoCache.Add(propertyInfo); + if (_propertyInfoCache.ContainsKey(typeof(TSource).Name)) + { + _propertyInfoCache[typeof(TSource).Name].Add(propertyInfo); + } + else + { + _propertyInfoCache.Add(typeof(TSource).Name, [propertyInfo]); + } } } } From 174316ef89f4c16d70957a611d70b9e378f97139 Mon Sep 17 00:00:00 2001 From: David Pavlovski Date: Fri, 28 Nov 2025 15:14:18 +0100 Subject: [PATCH 7/7] rename datashaper file --- .../DataShaping/DataShapingTests.cs | 40 +++++++++---------- .../ContentTypeChangesController.cs | 1 - .../{IDataShaper.cs => IDataShaperService.cs} | 2 +- Dappi.HeadlessCms/ServiceExtensions.cs | 2 +- .../{DataShaper.cs => DataShaperService.cs} | 2 +- Dappi.SourceGenerator/CrudGenerator.cs | 2 +- 6 files changed, 24 insertions(+), 25 deletions(-) rename Dappi.HeadlessCms/Interfaces/{IDataShaper.cs => IDataShaperService.cs} (76%) rename Dappi.HeadlessCms/Services/{DataShaper.cs => DataShaperService.cs} (94%) diff --git a/Dappi.HeadlessCms.Tests/DataShaping/DataShapingTests.cs b/Dappi.HeadlessCms.Tests/DataShaping/DataShapingTests.cs index 2ed0d044..c2a4c035 100644 --- a/Dappi.HeadlessCms.Tests/DataShaping/DataShapingTests.cs +++ b/Dappi.HeadlessCms.Tests/DataShaping/DataShapingTests.cs @@ -28,35 +28,35 @@ public class DataShapingTests [Fact] public void ShapeObject_Should_Throw_If_Property_Does_Not_Exist() { - var shaper = new DataShaper(); + var shaper = new DataShaperService(); Assert.Throws(() => shaper.ShapeObject(_dummyModels,"NonExistingProperty")); } [Fact] public void ShapeObject_Should_Throw_For_Private_Properties() { - var shaper = new DataShaper(); + var shaper = new DataShaperService(); Assert.Throws(() => shaper.ShapeObject(_dummyModel,"PrivateField")); } [Fact] public void ShapeObject_Should_Throw_For_Protected_Properties() { - var shaper = new DataShaper(); + var shaper = new DataShaperService(); Assert.Throws(() => shaper.ShapeObject(_dummyModel,"ProtectedField")); } [Fact] public void ShapeObject_Should_Throw_For_Internal_Properties() { - var shaper = new DataShaper(); + var shaper = new DataShaperService(); Assert.Throws(() => shaper.ShapeObject(_dummyModel,"InternalField")); } [Fact] public void ShapeObject_Should_Return_All_Properties_If_Fields_Param_Is_Null() { - var shaper = new DataShaper(); + var shaper = new DataShaperService(); IDictionary expandoObject = shaper.ShapeObject( _dummyModel,null); Assert.Equal(4, expandoObject.Count); @@ -69,7 +69,7 @@ public void ShapeObject_Should_Return_All_Properties_If_Fields_Param_Is_Null() [Fact] public void ShapeObject_Should_Return_All_Properties_If_Fields_Param_Is_EmptyString() { - var shaper = new DataShaper(); + var shaper = new DataShaperService(); IDictionary expandoObject = shaper.ShapeObject(_dummyModel,string.Empty); Assert.Equal(4, expandoObject.Count); @@ -82,7 +82,7 @@ public void ShapeObject_Should_Return_All_Properties_If_Fields_Param_Is_EmptyStr [Fact] public void ShapeObject_Should_Return_Only_Selected_Properties() { - var shaper = new DataShaper(); + var shaper = new DataShaperService(); const string fields = "Id,Name"; IDictionary expandoObject = shaper.ShapeObject(_dummyModel,fields); @@ -94,7 +94,7 @@ public void ShapeObject_Should_Return_Only_Selected_Properties() [Fact] public void ShapeObject_Should_Ignore_Case_Selected_Properties() { - var shaper = new DataShaper(); + var shaper = new DataShaperService(); const string fields = "Id,nAMe"; IDictionary expandoObject = shaper.ShapeObject(_dummyModel,fields); @@ -106,7 +106,7 @@ public void ShapeObject_Should_Ignore_Case_Selected_Properties() [Fact] public void ShapeObject_Should_Return_Correct_Values_For_All_Properties() { - var shaper = new DataShaper(); + var shaper = new DataShaperService(); IDictionary expandoObject = shaper.ShapeObject(_dummyModel,null); Assert.Equal(4,expandoObject.Count); @@ -119,7 +119,7 @@ public void ShapeObject_Should_Return_Correct_Values_For_All_Properties() [Fact] public void ShapeObject_Should_Return_Correct_Values_For_Selected_Properties() { - var shaper = new DataShaper(); + var shaper = new DataShaperService(); const string fields = "Id,Name,IsDeleted"; IDictionary expandoObject = shaper.ShapeObject(_dummyModel,"id,name,isDeleted"); @@ -132,7 +132,7 @@ public void ShapeObject_Should_Return_Correct_Values_For_Selected_Properties() [Fact] public void ShapeData_Should_Throw_If_Property_Does_Not_Exist() { - var shaper = new DataShaper(); + var shaper = new DataShaperService(); foreach (var dummyModel in _dummyModels) { @@ -143,7 +143,7 @@ public void ShapeData_Should_Throw_If_Property_Does_Not_Exist() [Fact] public void ShapeData_Should_Throw_For_Private_Properties() { - var shaper = new DataShaper(); + var shaper = new DataShaperService(); foreach (var dummyModel in _dummyModels) { Assert.Throws(() => shaper.ShapeObject(dummyModel,"PrivateField")); @@ -153,7 +153,7 @@ public void ShapeData_Should_Throw_For_Private_Properties() [Fact] public void ShapeData_Should_Throw_For_Protected_Properties() { - var shaper = new DataShaper(); + var shaper = new DataShaperService(); foreach (var dummyModel in _dummyModels) { @@ -164,7 +164,7 @@ public void ShapeData_Should_Throw_For_Protected_Properties() [Fact] public void ShapeData_Should_Throw_For_Internal_Properties() { - var shaper = new DataShaper(); + var shaper = new DataShaperService(); foreach (var dummyModel in _dummyModels) { Assert.Throws(() => shaper.ShapeObject(dummyModel,"InternalField")); @@ -174,7 +174,7 @@ public void ShapeData_Should_Throw_For_Internal_Properties() [Fact] public void ShapeData_Should_Return_All_Properties_If_Fields_Param_Is_Null() { - var shaper = new DataShaper(); + var shaper = new DataShaperService(); IEnumerable> expandoObjects = _dummyModels.Select(x => shaper.ShapeObject(x, null)).ToList(); @@ -191,7 +191,7 @@ public void ShapeData_Should_Return_All_Properties_If_Fields_Param_Is_Null() [Fact] public void ShapeData_Should_Return_All_Properties_If_Fields_Param_Is_EmptyString() { - var shaper = new DataShaper(); + var shaper = new DataShaperService(); IEnumerable> expandoObjects = _dummyModels.Select(x => shaper.ShapeObject(x, string.Empty)).ToList(); @@ -208,7 +208,7 @@ public void ShapeData_Should_Return_All_Properties_If_Fields_Param_Is_EmptyStrin [Fact] public void ShapeData_Should_Return_Selected_Properties() { - var shaper = new DataShaper(); + var shaper = new DataShaperService(); const string fields = "Id,Name"; IEnumerable> expandoObjects = _dummyModels.Select(x => shaper.ShapeObject(x, fields)); @@ -224,7 +224,7 @@ public void ShapeData_Should_Return_Selected_Properties() [Fact] public void ShapeData_Should_Ignore_Case() { - var shaper = new DataShaper(); + var shaper = new DataShaperService(); const string fields = "iD,nAMe"; IEnumerable> expandoObjects = _dummyModels.Select(x => shaper.ShapeObject(x, fields)).ToList(); @@ -240,7 +240,7 @@ public void ShapeData_Should_Ignore_Case() [Fact] public void ShapeData_Should_Return_Correct_Values_For_All_Properties() { - var shaper = new DataShaper(); + var shaper = new DataShaperService(); IEnumerable> expandoObjects = _dummyModels.Select(x => shaper.ShapeObject(x, null)).ToList(); @@ -259,7 +259,7 @@ public void ShapeData_Should_Return_Correct_Values_For_All_Properties() [Fact] public void ShapeData_Should_Return_Correct_Values_For_Selected_Properties() { - var shaper = new DataShaper(); + var shaper = new DataShaperService(); const string fields = "Id,Name,IsDeleted"; IEnumerable> expandoObjects = _dummyModels.Select(x => shaper.ShapeObject(x, fields)).ToList(); diff --git a/Dappi.HeadlessCms/Controllers/ContentTypeChangesController.cs b/Dappi.HeadlessCms/Controllers/ContentTypeChangesController.cs index 186d6822..b019d358 100644 --- a/Dappi.HeadlessCms/Controllers/ContentTypeChangesController.cs +++ b/Dappi.HeadlessCms/Controllers/ContentTypeChangesController.cs @@ -7,7 +7,6 @@ using Dappi.HeadlessCms.Models.Mapping; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; diff --git a/Dappi.HeadlessCms/Interfaces/IDataShaper.cs b/Dappi.HeadlessCms/Interfaces/IDataShaperService.cs similarity index 76% rename from Dappi.HeadlessCms/Interfaces/IDataShaper.cs rename to Dappi.HeadlessCms/Interfaces/IDataShaperService.cs index ca982ed7..2a5f6ecb 100644 --- a/Dappi.HeadlessCms/Interfaces/IDataShaper.cs +++ b/Dappi.HeadlessCms/Interfaces/IDataShaperService.cs @@ -2,7 +2,7 @@ namespace Dappi.HeadlessCms.Interfaces { - public interface IDataShaper + public interface IDataShaperService { ExpandoObject ShapeObject(TSource source, string? fields); } diff --git a/Dappi.HeadlessCms/ServiceExtensions.cs b/Dappi.HeadlessCms/ServiceExtensions.cs index 61279ef4..66422d27 100644 --- a/Dappi.HeadlessCms/ServiceExtensions.cs +++ b/Dappi.HeadlessCms/ServiceExtensions.cs @@ -56,7 +56,7 @@ public static IServiceCollection AddDappi( services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); services.AddDappiSwaggerGen(); services.AddControllers() diff --git a/Dappi.HeadlessCms/Services/DataShaper.cs b/Dappi.HeadlessCms/Services/DataShaperService.cs similarity index 94% rename from Dappi.HeadlessCms/Services/DataShaper.cs rename to Dappi.HeadlessCms/Services/DataShaperService.cs index 9b1fb746..aedb0752 100644 --- a/Dappi.HeadlessCms/Services/DataShaper.cs +++ b/Dappi.HeadlessCms/Services/DataShaperService.cs @@ -5,7 +5,7 @@ namespace Dappi.HeadlessCms.Services { - public class DataShaper : IDataShaper, IDisposable + public class DataShaperService : IDataShaperService, IDisposable { private const BindingFlags BindingFlags = System.Reflection.BindingFlags.IgnoreCase | System.Reflection.BindingFlags.Public | diff --git a/Dappi.SourceGenerator/CrudGenerator.cs b/Dappi.SourceGenerator/CrudGenerator.cs index f58c1e5f..75dc6383 100644 --- a/Dappi.SourceGenerator/CrudGenerator.cs +++ b/Dappi.SourceGenerator/CrudGenerator.cs @@ -68,7 +68,7 @@ namespace {item.RootNamespace}.Controllers; [Route(""api/[controller]"")] public partial class {item.ClassName}Controller( {dbContextData.ClassName} dbContext, - IDataShaper shaper, + IDataShaperService shaper, IMediaUploadService uploadService) : ControllerBase {{ [HttpGet]