Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Sub-Direction layer for user-facing Directions #1670

Open
wants to merge 11 commits into
base: develop
Choose a base branch
from

Conversation

omaiva
Copy link
Contributor

@omaiva omaiva commented Feb 11, 2025

  • Added SubDirection entity
  • Configured its relationship with Directions and InstitutionHierarchy
  • Updated usage of Directions in the project
  • Updated Service and Controller for Directions
  • Updated tests

Summary by CodeRabbit

  • New Features

    • Introduced a new method to filter directions based on specified parameters.
    • Added support for managing sub-directions within the application.
  • Improvements

    • Enhanced error messaging and response handling for create, update, and delete operations.
    • Improved handling of hierarchical data by focusing on sub-directions instead of directions.
  • Refactor

    • Streamlined internal handling of directional relationships for more robust and accurate data retrieval.
    • Updated data structures to accommodate the new sub-direction model across various services and controllers.

Copy link

coderabbitai bot commented Feb 11, 2025

Walkthrough

This pull request refactors the management of directional data across the codebase. The changes primarily replace the use of “Directions” with “SubDirections” in queries, mappings, entity models, and test data. Service methods, interfaces, and controller endpoints (including a new GetByFilter method) have been updated to return a structured result, with enhanced error handling and logging. Additionally, new configuration classes and test data generators for SubDirection have been introduced, and related tests have been modified accordingly.

Changes

File(s) Summary
Business Logic Services
ApplicationService.cs, WorkshopService.cs, DirectionService.cs, StatisticService.cs
Updated query inclusion and filtering logic from Directions to SubDirections; enhanced error handling, logging, and method return types to use Result<DirectionDto>.
Service Interfaces
IDirectionService.cs, ISensitiveDirectionService.cs
Modified method signatures and documentation to reflect Result<DirectionDto> return types instead of plain DTOs.
Mapping Configurations
ElasticProfile.cs, ExternalExportMappingProfile.cs, MappingProfile.cs
Adjusted mapping logic to source direction IDs and related data from SubDirections rather than Directions.
Data Access & Entity Models
Direction.cs, SubDirection.cs, SubDirectionConfiguration.cs, SubordinationStructure/InstitutionHierarchy.cs, OutOfSchoolDbContext.cs
Introduced the new SubDirection entity and configuration; removed old Directions properties in favor of SubDirections; added DbSet for SubDirection.
Repository Modifications
InstitutionHierarchyRepository.cs
Altered Create and Update methods to manage SubDirections instead of Directions.
Web API Controllers
DirectionController.cs
Added a new GetByFilter endpoint, updated GetById to return 404 for missing entities, and refined Create method error handling using a centralized handler.
Tests & Test Data Generators
AdminControllerTests.cs, DirectionControllerTests.cs, InstitutionHierarchyRepositoryTests.cs, DirectionSensitiveServiceTests.cs, DirectionServiceTests.cs, ExternalExportSubDirectionsTests.cs, InstitutionHierarchyServiceTest.cs, StatisticServiceTest.cs, TestDataGenerators (InstitutionHierarchyGenerator.cs, SubDirectionsGenerator.cs)
Updated tests to expect the structured Result wrappers and the new SubDirection model; introduced new generators for SubDirection test data.

Sequence Diagram(s)

sequenceDiagram
    participant C as Client
    participant DC as DirectionController
    participant DS as DirectionService
    participant DB as Database

    C->>DC: GET /v1/direction/filter?params
    DC->>DS: GetByFilter(filter)
    DS->>DB: Query using SubDirections
    DB-->>DS: Return filtered results
    DS-->>DC: Result (Success/NoContent)
    alt Valid result
        DC->>C: 200 OK with data
    else No data found
        DC->>C: 404 Not Found with message
    end
Loading
sequenceDiagram
    participant C as Client
    participant DC as DirectionController
    participant DS as DirectionService
    participant DB as Database

    C->>DC: POST /v1/direction with DirectionDto
    DC->>DS: Create(DirectionDto)
    DS->>DB: Save new Direction (with SubDirections)
    DB-->>DS: Persisted entity
    DS-->>DC: Result<DirectionDto>
    alt Creation successful
        DC->>C: 201 Created with entity data
    else Failure encountered
        DC->>C: Error response (wrapped in result)
    end
Loading

Poem

I'm a hopping bunny, code paths so bright,
SubDirections now guide my leaps just right.
Queries and mappings in a joyful parade,
With error-handling tunes freshly arrayed.
I nibble on changes with a cheerful delight,
Celebrating new routes from dusk until light!
Hop on, dear coder, the future's in sight!


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@omaiva omaiva linked an issue Feb 11, 2025 that may be closed by this pull request
@omaiva
Copy link
Contributor Author

omaiva commented Feb 11, 2025

@coderabbitai review

Copy link

coderabbitai bot commented Feb 11, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🔭 Outside diff range comments (2)
OutOfSchool/OutOfSchool.WebApi.Tests/Services/StatisticServiceTest.cs (1)

545-551: ⚠️ Potential issue

Fix incomplete test data in Application test case.

The SubDirection object in this test case is missing the DirectionId and Direction properties that are present in all other test cases. This inconsistency could affect test reliability.

Apply this diff to complete the test data:

                            new SubDirection
                            {
                                Id = 2,
+                               DirectionId = 2,
+                               Direction = new Direction
+                               {
+                                   Id = 2
+                               }
                            },
OutOfSchool/OutOfSchool.BusinessLogic/Services/SubordinationStructure/InstitutionHierarchyService.cs (1)

54-54: ⚠️ Potential issue

Update Create and Update methods to use SubDirections.

The Create and Update methods need to be updated to use the new SubDirection relationship instead of directly using Directions.

Apply this diff to fix the inconsistency:

-        var newInstitutionHierarchy = await repository.Create(institutionHierarchy, dto.Directions.Select(d => d.Id).ToList()).ConfigureAwait(false);
+        var newInstitutionHierarchy = await repository.Create(institutionHierarchy, dto.SubDirections.Select(s => s.Direction.Id).ToList()).ConfigureAwait(false);

-            .Update(
-                institutionHierarchy,
-                dto.Directions.Select(d => d.Id).ToList())
+            .Update(
+                institutionHierarchy,
+                dto.SubDirections.Select(s => s.Direction.Id).ToList())

Also applies to: 236-236

🧹 Nitpick comments (27)
OutOfSchool/OutOfSchool.WebApi.Tests/Controllers/AdminControllerTests.cs (1)

219-239: Consider adding test coverage for failed results.

While the tests cover successful updates and invalid model states, they don't verify how the controller handles failed results from the service layer (e.g., when the service returns Result<DirectionDto>.Failed()).

Consider adding a test case like this:

+[Test]
+public async Task UpdateDirections_WhenServiceReturnsFailed_ReturnsBadRequestObjectResult()
+{
+    // Arrange
+    controller.ControllerContext.HttpContext = fakeHttpContext;
+    controller.ControllerContext.HttpContext.SetContextUser(Role.TechAdmin);
+    var changedDirection = new DirectionDto()
+    {
+        Id = 1,
+        Title = "ChangedTitle",
+    };
+    var returnedResult = Result<DirectionDto>.Failed(new OperationError
+    {
+        Code = "400",
+        Description = "Update failed",
+    });
+    sensitiveDirectionService.Setup(x => x.Update(changedDirection)).ReturnsAsync(returnedResult);
+
+    // Act
+    var result = await controller.UpdateDirections(changedDirection).ConfigureAwait(false);
+
+    // Assert
+    Assert.That(result, Is.TypeOf<BadRequestObjectResult>());
+    Assert.That((result as BadRequestObjectResult).StatusCode, Is.EqualTo(400));
+}

Also applies to: 241-255

OutOfSchool/OutOfSchool.WebApi.Tests/Services/StatisticServiceTest.cs (3)

59-80: Enhance test coverage for SubDirection functionality.

The existing tests verify the workshop retrieval but don't explicitly validate the new SubDirection layer. Consider adding test cases that verify:

  1. Correct mapping of SubDirection to Direction
  2. Proper filtering based on SubDirection properties

Also applies to: 82-104


248-476: Reduce test data duplication using helper methods.

The SubDirection test data structure is repeated across WithWorkshops, WithWorkshopsIncludingCATOTTG, and WithApplications methods. Consider extracting the common SubDirection setup into a helper method.

Example helper method:

private SubDirection CreateSubDirection(int id)
{
    return new SubDirection
    {
        Id = id,
        DirectionId = id,
        Direction = new Direction
        {
            Id = id
        }
    };
}

Also applies to: 488-626


106-156: Enhance assertions to verify SubDirection properties.

The current assertions verify the overall results but don't explicitly check the SubDirection properties. Consider adding specific assertions to verify the SubDirection-Direction relationship.

Example assertion:

result
    .Should()
    .Match<Workshop>(w => 
        w.InstitutionHierarchy.SubDirections.All(sd => 
            sd.DirectionId == sd.Direction.Id));
OutOfSchool/OutOfSchool.WebApi.Tests/Controllers/DirectionControllerTests.cs (1)

153-165: Add test coverage for Result failure scenarios.

The Create test only covers the success case. Consider adding tests for failure scenarios using Result, such as:

  • Business rule violations
  • Validation failures
  • Conflict scenarios

Example test:

[Test]
public async Task Create_WhenBusinessRuleViolated_ReturnsBadRequest()
{
    // Arrange
    var error = "Business rule violated";
    var returnedResult = Result<DirectionDto>.Failed(error);
    service.Setup(x => x.Create(direction)).ReturnsAsync(returnedResult);

    // Act
    var result = await controller.Create(direction).ConfigureAwait(false) as BadRequestObjectResult;

    // Assert
    Assert.That(result, Is.Not.Null);
    Assert.That(result.StatusCode, Is.EqualTo(400));
    Assert.That(result.Value, Is.EqualTo(error));
}
OutOfSchool/OutOfSchool.BusinessLogic/Util/Mapping/ElasticProfile.cs (1)

156-156: Consider adding null checks for robustness.

The mapping assumes that both SubDirection and Direction entities are not null.

Add null checks to prevent potential NullReferenceException:

-opt => opt.MapFrom(src => src.InstitutionHierarchy.SubDirections.Where(x => !x.Direction.IsDeleted).Select(d => d.DirectionId)))
+opt => opt.MapFrom(src => src.InstitutionHierarchy?.SubDirections?.Where(x => x.Direction != null && !x.Direction.IsDeleted).Select(d => d.DirectionId) ?? Enumerable.Empty<long>()))
OutOfSchool/OutOfSchool.WebApi.Tests/Services/DirectionServiceTests.cs (3)

103-103: Consider a more specific test name.

The test name could be more descriptive about the specific failure condition being tested.

Consider renaming to Create_DuplicateTitle_ReturnsFailedResult to better describe the exact validation being tested.


223-225: Enhance test coverage for SubDirections relationship.

While the filter logic has been updated to use SubDirections, the test assertions only verify the Direction properties.

Consider adding assertions to verify the SubDirections relationship:

 // Assert
 Assert.True(result.Entities.All(d => d.Title == expectedDto.Title));
+Assert.That(expected.FirstOrDefault().SubDirections, Is.Not.Empty, "Direction should have associated SubDirections");
+Assert.That(expected.FirstOrDefault().SubDirections.SelectMany(s => s.InstitutionHierarchies), Is.Not.Empty, "SubDirections should have associated InstitutionHierarchies");

Also applies to: 260-262


313-330: Enhance test data coverage.

The test data could be more comprehensive. Currently, only one Direction has SubDirections, which might miss testing important scenarios.

Consider adding SubDirections to other test Directions to ensure proper testing of various scenarios:

 new Direction()
 {
     Title = "Test1",
     Description = "Test1",
+    SubDirections = new List<SubDirection>()
+    {
+        new SubDirection()
+        {
+            Title = "Test1Sub",
+            Description = "Test1Sub",
+            InstitutionHierarchies = new List<InstitutionHierarchy>()
+            {
+                new InstitutionHierarchy()
+                {
+                    Id = Guid.NewGuid(),
+                    Title = "Title1",
+                    HierarchyLevel = 1,
+                    InstitutionId = new Guid("af475193-6a1e-4a75-9ba3-439c4300f771"),
+                },
+            },
+        }
+    }
 },
OutOfSchool/OutOfSchool.BusinessLogic/Util/MappingProfile.cs (2)

505-507: Consider adding XML documentation for Direction mapping.

The mapping configuration for Direction has been updated to ignore SubDirections. Consider adding XML documentation to explain the relationship between Direction and SubDirection entities, similar to the documentation provided for other mappings (e.g., lines 243-252).

+        /// <summary>
+        /// Maps Direction entity to DirectionDto.
+        /// Note: SubDirections are ignored in the reverse mapping as they are managed through the SubDirection entity.
+        /// </summary>
         CreateSoftDeletedMap<DirectionDto, Direction>()
             .ForMember(dest => dest.SubDirections, opt => opt.Ignore())
             .ForMember(c => c.UpdatedAt, m => m.Ignore());

493-494: Remove or address the warning comment.

The warning comment indicates that the mapping for ShortUserDto to AdminDto is temporary and needs to be removed or refactored. Consider creating a new issue to track this technical debt.

Would you like me to create a new issue to track the removal or refactoring of this temporary mapping?

OutOfSchool/OutOfSchool.BusinessLogic/Services/DirectionService.cs (5)

110-110: Typo: “succesfully” → “successfully.”
Please correct the misspelling to maintain professional logging output.

- logger.LogDebug("Direction with Id = {id} succesfully deleted.", id);
+ logger.LogDebug("Direction with Id = {id} successfully deleted.", id);

116-117: Reconsider concurrency exception usage.
Throwing a DbUpdateConcurrencyException for a missing record can be misleading. Consider a more descriptive custom exception or a Result failure to ensure clarity.


160-160: Redundant cast.
Casting g.Count() to int? is superfluous since Count() is already an int. Consider removing it unless needed for a future requirement.

- WorkshopsCount = g.Count() as int?,
+ WorkshopsCount = g.Count(),

230-232: Typo: “succesfully” → “successfully.”
Please fix the spelling to maintain consistency in logs.

- logger.LogDebug("Direction with Id = {id} updated succesfully.", direction?.Id);
+ logger.LogDebug("Direction with Id = {id} updated successfully.", direction?.Id);

287-287: Keep block style consistent.
The else { statement uses an inline brace, whereas other blocks use new lines. For readability, consider a consistent code style.

OutOfSchool/OutOfSchool.DataAccess/Models/Direction.cs (1)

24-24: Replacing InstitutionHierarchies with SubDirections.
This transition suggests a pivot in data modeling towards a more granular sub-direction structure. Ensure SubDirections is initialized (= new();) if you want to avoid null references.

- public virtual List<SubDirection> SubDirections { get; set; }
+ public virtual List<SubDirection> SubDirections { get; set; } = new();
OutOfSchool/OutOfSchool.DataAccess/Models/SubDirection.cs (1)

18-20: Description property usage.
The optional 500-length description is a practical limit, but consider validating user input to avoid large manual expansions.

OutOfSchool/OutOfSchool.DataAccess/Models/Configurations/SubDirectionConfiguration.cs (1)

1-23: LGTM! Consider adding XML documentation.

The entity configuration is well-structured with proper relationship definitions and performance considerations (index on IsDeleted). The use of DeleteBehavior.Restrict is appropriate to prevent unintended cascading deletes.

Consider adding XML documentation to describe the purpose of this configuration class and its relationships:

+/// <summary>
+/// Entity Framework configuration for the SubDirection entity.
+/// Configures relationships with Direction and InstitutionHierarchy entities.
+/// </summary>
 public class SubDirectionConfiguration : IEntityTypeConfiguration<SubDirection>
OutOfSchool/OutOfSchool.DataAccess/Models/SubordinationStructure/InstitutionHierarchy.cs (1)

30-30: Initialize the collection property.

The virtual collection property should be initialized to prevent potential null reference exceptions.

-    public virtual List<SubDirection> SubDirections { get; set; }
+    public virtual List<SubDirection> SubDirections { get; set; } = new();
OutOfSchool/Tests/OutOfSchool.Tests.Common/TestDataGenerators/SubDirectionsGenerator.cs (2)

9-11: Enhance test data generation rules.

The current implementation might benefit from additional rules and a larger Id range:

 private static readonly Faker<SubDirection> faker = new Faker<SubDirection>()
-    .RuleFor(x => x.Id, f => f.Random.Long(min: 1, max: 1_000_000))
+    .RuleFor(x => x.Id, f => f.Random.Long(min: 1, max: long.MaxValue))
+    .RuleFor(x => x.IsDeleted, f => f.Random.Bool(0.1f))  // 10% chance of being deleted
+    .RuleFor(x => x.Description, f => f.Lorem.Paragraph())
     .RuleFor(x => x.Title, f => f.Company.CompanyName());

15-16: Consider adding parallel generation for large datasets.

For large lists of direction IDs, consider using parallel processing:

 public static List<SubDirection> Generate(List<long> directionIds) =>
-    directionIds.Select(id => Generate(id)).ToList();
+    directionIds.AsParallel()
+        .Select(id => Generate(id))
+        .ToList();
OutOfSchool/Tests/OutOfSchool.Tests.Common/TestDataGenerators/InstitutionHierarchyGenerator.cs (1)

41-41: Consider adding null check and filtering deleted SubDirections.

The flattening operation might throw if any Direction in the list is null or if SubDirections is null. Additionally, consider filtering out deleted SubDirections if applicable.

-        hierarchy.SubDirections = directions.SelectMany(d => d.SubDirections).ToList();
+        hierarchy.SubDirections = directions
+            ?.Where(d => d != null)
+            .SelectMany(d => d.SubDirections?.Where(sd => !sd.IsDeleted) ?? Enumerable.Empty<SubDirection>())
+            .ToList() ?? [];
OutOfSchool/OutOfSchool.BusinessLogic/Util/Mapping/ExternalExportMappingProfile.cs (1)

82-82: Consider adding filtering for non-deleted Directions.

For consistency with the Workshop mapping, consider filtering out deleted Directions here as well.

-            .ForMember(dest => dest.DirectionIds, opt => opt.MapFrom(src => src.SubDirections.Select(x => x.DirectionId)));
+            .ForMember(dest => dest.DirectionIds, opt => opt.MapFrom(src => src.SubDirections.Where(x => !x.Direction.IsDeleted).Select(x => x.DirectionId)));
OutOfSchool/OutOfSchool.WebApi/Controllers/V1/DirectionController.cs (2)

130-152: Consider consolidating exception handling.

The exception handling could be simplified by combining the catch blocks since they perform the same action.

         try
         {
             directionDto.Id = default;
             var response = await service.Create(directionDto).ConfigureAwait(false);
             if (response != null && response.Succeeded)
             {
                 return CreatedAtAction(
                     nameof(GetById),
                     new { id = response.Value.Id, },
                     response.Value);
             }
             else
             {
                 return HandleServiceRespone(response);
             }
         }
-        catch (ArgumentException ex)
-        {
-            return BadRequest(ex.Message);
-        }
-        catch (Exception ex)
-        {
-            return BadRequest(ex.Message);
-        }
+        catch (Exception ex) when (ex is ArgumentException || ex is Exception)
+        {
+            return BadRequest(ex.Message);
+        }

154-174: Fix typo in method name.

The method name contains a typo: HandleServiceRespone should be HandleServiceResponse.

-    private IActionResult HandleServiceRespone<T>(Result<T> response)
+    private IActionResult HandleServiceResponse<T>(Result<T> response)
OutOfSchool/OutOfSchool.DataAccess/Repository/InstitutionHierarchyRepository.cs (1)

29-59: Consider moving business logic to a service layer.

The repository is currently handling complex business logic related to Direction-SubDirection relationships. This violates the Single Responsibility Principle and makes the code harder to maintain and test.

Consider:

  1. Moving the Direction-SubDirection relationship logic to a dedicated service
  2. Keeping the repository focused on basic CRUD operations
  3. Using a Unit of Work pattern for transaction management

Example service structure:

public interface IInstitutionHierarchyService
{
    Task<InstitutionHierarchy> CreateWithDirections(InstitutionHierarchy entity, List<long> directionsIds);
    Task<InstitutionHierarchy> UpdateWithDirections(InstitutionHierarchy entity, List<long> directionsIds);
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 667ee00 and bb39b77.

📒 Files selected for processing (28)
  • OutOfSchool/OutOfSchool.BusinessLogic/Services/ApplicationService.cs (1 hunks)
  • OutOfSchool/OutOfSchool.BusinessLogic/Services/Database/WorkshopService.cs (1 hunks)
  • OutOfSchool/OutOfSchool.BusinessLogic/Services/DirectionService.cs (7 hunks)
  • OutOfSchool/OutOfSchool.BusinessLogic/Services/ExternalExportService.cs (1 hunks)
  • OutOfSchool/OutOfSchool.BusinessLogic/Services/IDirectionService.cs (2 hunks)
  • OutOfSchool/OutOfSchool.BusinessLogic/Services/ISensitiveDirectionService.cs (1 hunks)
  • OutOfSchool/OutOfSchool.BusinessLogic/Services/StatisticService.cs (2 hunks)
  • OutOfSchool/OutOfSchool.BusinessLogic/Services/SubordinationStructure/InstitutionHierarchyService.cs (1 hunks)
  • OutOfSchool/OutOfSchool.BusinessLogic/Util/Mapping/ElasticProfile.cs (1 hunks)
  • OutOfSchool/OutOfSchool.BusinessLogic/Util/Mapping/ExternalExportMappingProfile.cs (2 hunks)
  • OutOfSchool/OutOfSchool.BusinessLogic/Util/MappingProfile.cs (6 hunks)
  • OutOfSchool/OutOfSchool.DataAccess/Models/Configurations/SubDirectionConfiguration.cs (1 hunks)
  • OutOfSchool/OutOfSchool.DataAccess/Models/Direction.cs (1 hunks)
  • OutOfSchool/OutOfSchool.DataAccess/Models/SubDirection.cs (1 hunks)
  • OutOfSchool/OutOfSchool.DataAccess/Models/SubordinationStructure/InstitutionHierarchy.cs (1 hunks)
  • OutOfSchool/OutOfSchool.DataAccess/OutOfSchoolDbContext.cs (2 hunks)
  • OutOfSchool/OutOfSchool.DataAccess/Repository/InstitutionHierarchyRepository.cs (2 hunks)
  • OutOfSchool/OutOfSchool.WebApi.Tests/Controllers/AdminControllerTests.cs (2 hunks)
  • OutOfSchool/OutOfSchool.WebApi.Tests/Controllers/DirectionControllerTests.cs (4 hunks)
  • OutOfSchool/OutOfSchool.WebApi.Tests/Services/Database/InstitutionHierarchyRepositoryTests.cs (1 hunks)
  • OutOfSchool/OutOfSchool.WebApi.Tests/Services/DirectionSensitiveServiceTests.cs (3 hunks)
  • OutOfSchool/OutOfSchool.WebApi.Tests/Services/DirectionServiceTests.cs (7 hunks)
  • OutOfSchool/OutOfSchool.WebApi.Tests/Services/ExternalExportSubDirectionsTests.cs (1 hunks)
  • OutOfSchool/OutOfSchool.WebApi.Tests/Services/InstitutionHierarchyServiceTest.cs (5 hunks)
  • OutOfSchool/OutOfSchool.WebApi.Tests/Services/StatisticServiceTest.cs (12 hunks)
  • OutOfSchool/OutOfSchool.WebApi/Controllers/V1/DirectionController.cs (3 hunks)
  • OutOfSchool/Tests/OutOfSchool.Tests.Common/TestDataGenerators/InstitutionHierarchyGenerator.cs (3 hunks)
  • OutOfSchool/Tests/OutOfSchool.Tests.Common/TestDataGenerators/SubDirectionsGenerator.cs (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: test (windows-latest)
🔇 Additional comments (48)
OutOfSchool/OutOfSchool.WebApi.Tests/Controllers/AdminControllerTests.cs (2)

230-231: LGTM! The test correctly handles the new Result type.

The test has been updated to use Result<DirectionDto> instead of directly returning DirectionDto, which aligns with the service layer changes.


268-269: LGTM! The test maintains consistency with the service layer changes.

The test correctly uses Result<DirectionDto> for the non-admin user scenario, maintaining consistency with the service layer changes.

OutOfSchool/OutOfSchool.WebApi.Tests/Controllers/DirectionControllerTests.cs (4)

13-13: LGTM!

The addition of the OutOfSchool.BusinessLogic.Common namespace is necessary to support the Result return type changes.


139-150: LGTM! Improved error handling with proper HTTP status codes.

The test now correctly expects a NotFound response instead of null, which is a better REST practice.


156-157: LGTM! Properly handling Result wrapper.

The test correctly sets up the service mock to return a Result and verifies the success case.


196-209: LGTM! Added necessary IDs to test data.

The addition of sequential IDs to the test data enables proper testing of ID-based operations.

OutOfSchool/OutOfSchool.BusinessLogic/Util/Mapping/ElasticProfile.cs (1)

156-156: LGTM! The mapping change aligns with the SubDirection layer introduction.

The mapping correctly retrieves direction IDs through the new SubDirection relationship.

Please verify that:

  1. All SubDirections have valid DirectionId values
  2. The deletion check on Direction (parent) instead of SubDirection (child) maintains the expected filtering behavior

Run this script to verify the relationships and values:

✅ Verification successful

🏁 Script executed:

#!/bin/bash
# Description: Verify SubDirection relationships and values

# Check for SubDirections with null or invalid DirectionId
ast-grep --pattern 'class SubDirection {
  $$$
  DirectionId $_
  $$$
}'

# Check for SubDirection usage patterns
rg -A 5 'SubDirection.*Direction.*IsDeleted'

Length of output: 2704


Mapping configuration update for SubDirections Approved

The change to map direction IDs through the SubDirections relationship—with a deletion check on the parent Direction—is consistent across the codebase. Usage in both the WorkshopService.cs and ExternalExportMappingProfile.cs confirms this pattern is intentional.

  • Ensure all SubDirections indeed have valid DirectionId values.
  • Verify that checking !x.Direction.IsDeleted on the parent yields the desired filtering behavior.

Overall, the modifications meet the PR objectives.

OutOfSchool/OutOfSchool.BusinessLogic/Util/MappingProfile.cs (2)

46-47: Consistent mapping of DirectionIds across entities.

The mapping configurations for Workshop, WorkshopCreateRequestDto, and CompetitiveEvent have been consistently updated to source DirectionIds from SubDirections.DirectionId instead of Directions.Id. This change aligns with the new entity relationships.

Also applies to: 166-167, 780-781


471-472: Verify the impact of InstitutionHierarchy mapping changes.

The mapping configuration for InstitutionHierarchy has been updated to:

  1. Map Directions from SubDirections.Direction
  2. Ignore SubDirections in the reverse mapping

While the changes look correct, ensure that:

  1. The Direction navigation property is properly loaded when retrieving SubDirections
  2. Any code that depends on the Directions property is updated to handle the new mapping

Also applies to: 474-475

❌ Verification inconclusive

🏁 Script executed:

#!/bin/bash
# Description: Check for direct usage of InstitutionHierarchy.Directions property
# to identify potential breaking changes

# Search for direct property access
echo "Searching for direct property access..."
rg "InstitutionHierarchy\.Directions" -A 5

# Search for LINQ queries
echo "Searching for LINQ queries..."
rg "from\s+.*\s+in\s+.*\.InstitutionHierarchy\.Directions" -A 5

Length of output: 1180


Action: Review EF Query Configuration and ExternalExportService Usage of InstitutionHierarchy.Directions

The mapping update in the profile is correct in mapping Directions from SubDirections by selecting the nested Direction property. Our static search confirms that ExternalExportService directly references InstitutionHierarchy.Directions (as found in the string concatenation for export). However, we have not seen any clear evidence (via eager‑loading configurations in search results) that the retrieval of InstitutionHierarchy ensures its SubDirections—and thus their nested Direction properties—are fully loaded.

  • Impact Point: ExternalExportService depends on InstitutionHierarchy.Directions to be populated.
  • Next Step: Please verify that the EF queries retrieving InstitutionHierarchy include appropriate eager-loading (e.g. using .Include(s => s.SubDirections).ThenInclude(sd => sd.Direction)) so that the Direction navigation property is available at runtime.
OutOfSchool/OutOfSchool.BusinessLogic/Services/DirectionService.cs (20)

58-60: Refactored return type to a wrapped result.
Switching from returning a raw DTO to Result<DirectionDto> is a solid approach for structured error handling and success/failure encapsulation.


64-68: Validation errors are now surfaced appropriately.
Returning Result<DirectionDto>.Failed upon encountering validation errors is a clean way to notify callers of specific issues without throwing exceptions.


72-74: Successful creation logging.
Logging the newly created entity’s ID is helpful for debugging. The final Result<DirectionDto>.Success result looks consistent with the new pattern.


80-80: Consistent debug-level logging.
Lowering the log level from Information to Debug is acceptable if you prefer less verbosity in production.


94-94: Verify EF query for SubDirections usage.
Using .Any(d => d.DirectionId == id) is correct for a simple existence check. However, ensure your database indexing covers frequent lookups by DirectionId to prevent potential performance bottlenecks.


124-128: Retrieval logging is consistent.
Debug-level logging for fetching all directions is consistent with the rest of the service.


133-136: Introduce new filtering for directions.
Returning a SearchResult<DirectionDto> while logging at Debug level is a good practice for tracking advanced queries without cluttering logs.


148-148: Loading SubDirections eagerly.
Using includeProperties: "SubDirections" ensures related data is available, but confirm if this is always needed to optimize performance.


153-153: Check potential performance for nested ‘Any’ calls.
Further filtering with .Any(...) can lead to multiple queries or more complex SQL. Make sure heavy usage is tested for performance.


170-170: Debug logging for final results.
Good to see the total count of returned directions, which aids in verifying query correctness.


200-202: Refactored update method to return a ‘Result’.
Aligning the update method with the same return signature improves consistency for error and success handling.


204-212: Guarding against null DTO.
This ensures immediate feedback if the caller passes a null object, avoiding potential null reference issues downstream.


219-224: Handling non-existent Direction on update.
Returning a 404 within the Result object is consistent with RESTful patterns, making it clear that the resource is not found.


235-237: Validation function returning a collection of errors.
Returning a list instead of throwing allows for graceful handling of multiple validation failures.


239-239: Check concurrency for title uniqueness.
Using .Any() checks if a direction with the same title already exists. Ensure concurrency is handled if multiple create/update calls happen simultaneously.


241-247: Warning log for duplicate direction data.
The approach of logging and adding to an error list is consistent with the newly introduced Result error pattern.


250-250: Returning validation errors.
Short, straightforward approach to packaging validation failures.


260-261: Filter expansion to SubDirections.
Including direction.SubDirections.Any(...) in the filter broadens the search. Double-check indexing on the related tables for consistent performance.


272-273: Ministry Admin filter on SubDirections.
Chaining .Any(...) calls is logical. Just confirm performance is acceptable for large sets of SubDirections.


281-282: Region Admin filter on SubDirections.
Similar approach as the ministry admin logic. Keep an eye on performance with large data.

OutOfSchool/OutOfSchool.DataAccess/Models/SubDirection.cs (3)

1-5: Introduction of SubDirection entity.
Defining a separate class for SubDirections is an effective approach to modularizing the direction model.


6-16: Validation attributes on Title.
Marking the title as required and imposing length constraints ensures the user can’t enter invalid or excessively long titles.


21-24: Navigation properties for relationships.
Associate SubDirection with Direction and InstitutionHierarchies ensures a well-defined data model. Confirm your mappings are correct in EF configurations.

OutOfSchool/Tests/OutOfSchool.Tests.Common/TestDataGenerators/InstitutionHierarchyGenerator.cs (1)

15-15: LGTM! Initialization of SubDirections collection.

The change correctly initializes an empty SubDirections collection using the new C# collection expression syntax.

OutOfSchool/OutOfSchool.WebApi.Tests/Services/Database/InstitutionHierarchyRepositoryTests.cs (1)

60-60: LGTM! Updated test to use SubDirections.

The test correctly reflects the transition from Directions to SubDirections by selecting DirectionId from the SubDirections collection.

OutOfSchool/OutOfSchool.BusinessLogic/Util/Mapping/ExternalExportMappingProfile.cs (1)

41-41: LGTM! Added proper filtering for non-deleted Directions.

The mapping correctly filters out deleted Directions when selecting DirectionIds from SubDirections.

OutOfSchool/OutOfSchool.WebApi.Tests/Services/DirectionSensitiveServiceTests.cs (3)

88-88: LGTM! Assertion updated to handle Result wrapper.

The assertion has been correctly updated to access the Title property through the Result wrapper's Value property.


93-104: LGTM! Test updated to verify new error handling.

The test has been properly updated to:

  1. Check for failed result status
  2. Verify the correct error code is returned
    This aligns well with the service's new error handling approach using Result.

131-148: LGTM! Data seeding updated for new entity model.

The test data setup has been correctly updated to reflect the new entity relationships:

  • Direction contains SubDirections
  • SubDirection contains InstitutionHierarchies
OutOfSchool/OutOfSchool.WebApi.Tests/Services/ExternalExportSubDirectionsTests.cs (1)

143-147: LGTM! Test data setup enhanced with SubDirections.

The test data generation has been properly updated to:

  1. Generate SubDirections using Direction IDs
  2. Add SubDirections to the context before Directions to maintain referential integrity
OutOfSchool/OutOfSchool.WebApi/Controllers/V1/DirectionController.cs (2)

57-73: LGTM! Well-structured filter endpoint added.

The new GetByFilter endpoint is concisely implemented with proper:

  • Documentation
  • Response type definitions
  • Result handling using ProtectAndMap

81-99: LGTM! Enhanced error handling for GetById.

The method has been improved with:

  1. Proper null check
  2. Specific error message for NotFound response
  3. Updated API documentation
OutOfSchool/OutOfSchool.BusinessLogic/Services/StatisticService.cs (2)

86-89: LGTM! Query updated for new entity model.

The query has been properly updated to:

  1. Use SubDirections relationship
  2. Group by DirectionId maintaining data consistency

191-191: Verify performance impact of additional includes.

The addition of SubDirections and Direction includes will generate additional joins. Consider monitoring query performance with larger datasets.

✅ Verification successful

🏁 Script executed:

#!/bin/bash
# Description: Check for potential N+1 queries in workshop loading

# Search for related repository methods that might trigger lazy loading
rg -A 5 "workshopRepository.Get|popularWorkshops"

Length of output: 25811


Performance Impact Verification for Extra Includes

The extra include chain in the query now joins InstitutionHierarchy → SubDirections → Direction in a single query. This approach avoids N+1 issues since EF Core generates one complex query rather than multiple round trips; however, the additional joins can increase query complexity and memory consumption. No evidence of an N+1 query pattern was found in the repository searches, but with larger datasets the join cost may negatively impact performance.

  • The eager loading is correctly implemented in a single query.
  • Additional joins are expected with this pattern; monitor execution time with sizeable data.
  • Consider performance profiling (e.g., via EF Core logging) in a production-like environment to ensure acceptable performance.
OutOfSchool/OutOfSchool.DataAccess/OutOfSchoolDbContext.cs (1)

149-149: LGTM!

The changes correctly introduce the new SubDirection entity by adding the DbSet property and applying its configuration.

Also applies to: 204-204

OutOfSchool/OutOfSchool.BusinessLogic/Services/ExternalExportService.cs (1)

229-229: LGTM!

The property inclusion is correctly updated to use "SubDirections" instead of "Directions", aligning with the new entity structure.

OutOfSchool/OutOfSchool.WebApi.Tests/Services/InstitutionHierarchyServiceTest.cs (1)

68-68: LGTM!

The test cases are correctly updated to use SubDirections instead of Directions while maintaining the same test coverage and assertions.

Also applies to: 233-233, 251-251, 275-275, 284-284

OutOfSchool/OutOfSchool.BusinessLogic/Services/ApplicationService.cs (1)

362-362: LGTM!

The entity inclusion chain is correctly updated to load SubDirections and their associated Direction, maintaining proper data relationships.

OutOfSchool/OutOfSchool.BusinessLogic/Services/Database/WorkshopService.cs (1)

927-927: LGTM! Consider monitoring query performance.

The change correctly implements filtering through the new SubDirection entity while maintaining proper soft delete checks.

Since this change adds an additional level of navigation (SubDirections -> Direction), consider monitoring query performance. You can use the following script to analyze the generated SQL:

✅ Verification successful

🏁 Script executed:

#!/bin/bash
# Description: Search for similar query patterns to identify potential performance impacts

# Look for similar navigation patterns that might benefit from optimization
ast-grep --pattern 'SubDirections.Any(d => !d.Direction.IsDeleted'

# Look for potential eager loading configurations that might help optimize the query
rg -A 5 'includeProperties.*SubDirections'

Length of output: 2171


The SubDirection filtering condition is implemented correctly and aligns with similar patterns in the codebase.

  • The usage of InstitutionHierarchy.SubDirections.Any(d => !d.Direction.IsDeleted && d.DirectionId == direction) is consistent with the refactoring observed in DirectionService.cs and its tests.
  • As highlighted, the additional navigation layer through SubDirections should be monitored for performance impacts, but its implementation is sound.

Comment on lines 163 to 170
public async Task GetById_WhenIdIsInvalid_ThrowsArgumentOutOfRangeException(long id)
{
// Act and Assert
Assert.ThrowsAsync<ArgumentOutOfRangeException>(
async () => await service.GetById(id).ConfigureAwait(false));
// Act
var result = await service.GetById(id).ConfigureAwait(false);

// Assert
Assert.IsNull(result);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Update test name to match new behavior.

The test name suggests it's expecting an exception, but the implementation checks for a null result.

Rename the test to match the actual behavior being tested:

-public async Task GetById_WhenIdIsInvalid_ThrowsArgumentOutOfRangeException(long id)
+public async Task GetById_WhenIdIsInvalid_ReturnsNull(long id)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public async Task GetById_WhenIdIsInvalid_ThrowsArgumentOutOfRangeException(long id)
{
// Act and Assert
Assert.ThrowsAsync<ArgumentOutOfRangeException>(
async () => await service.GetById(id).ConfigureAwait(false));
// Act
var result = await service.GetById(id).ConfigureAwait(false);
// Assert
Assert.IsNull(result);
}
public async Task GetById_WhenIdIsInvalid_ReturnsNull(long id)
{
// Act
var result = await service.GetById(id).ConfigureAwait(false);
// Assert
Assert.IsNull(result);
}

Comment on lines 88 to 92
var institutionHierarchies = await repository.Get(asNoTracking: true)
.Include(x => x.Directions)
.Include(x => x.SubDirections).ThenInclude(s => s.Direction)
.Include(x => x.Institution)
.ToListAsync()
.ConfigureAwait(false);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

⚠️ Potential issue

🧩 Analysis chain

Update data fetching to use new SubDirection relationship.

The GetAll method now correctly includes SubDirections and their related Direction entities. However, the Create and Update methods still use the old Directions relationship, which could lead to runtime errors or data inconsistency.

Let's verify the impact of this change:


🏁 Script executed:

#!/bin/bash
# Description: Check for inconsistencies in the usage of Directions vs SubDirections

# Search for Directions usage in the codebase
echo "Searching for Directions usage..."
rg -A 5 "Directions\." --type cs

# Search for SubDirections usage in the codebase
echo "Searching for SubDirections usage..."
rg -A 5 "SubDirections\." --type cs

# Search for Direction-related entity configurations
echo "Searching for Direction configurations..."
ast-grep --pattern $'class DirectionConfiguration : IEntityTypeConfiguration<Direction>
{
  $$$
}'

# Search for SubDirection-related entity configurations
echo "Searching for SubDirection configurations..."
ast-grep --pattern $'class SubDirectionConfiguration : IEntityTypeConfiguration<SubDirection>
{
  $$$
}'

Length of output: 47001


Inconsistency Identified Between Data Fetching and Modification Methods

The GetAll method now correctly loads the SubDirection relationship, but the Create/Update flows still depend on the old dto.Directions—which are then translated in the repository via a lookup on dbContext.Directions to retrieve SubDirections. This mismatch can lead to runtime surprises or data inconsistency if the DTO isn’t updated or the repository logic isn’t adjusted accordingly.

  • Ensure the Create/Update methods use a DTO that reflects the new SubDirection entity.
  • Confirm that the repository methods operating on these IDs correctly map to SubDirections as expected.

Comment on lines 13 to 14
/// The task result contains a <see cref="DirectionCreateUpdateDto"/> that was updated.</returns>
Task<Result<DirectionDto>> Update(DirectionDto dto);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix incorrect XML documentation.

The XML documentation references DirectionCreateUpdateDto, but the method returns Result<DirectionDto>.

-    /// The task result contains a <see cref="DirectionCreateUpdateDto"/> that was updated.</returns>
+    /// The task result contains a <see cref="DirectionDto"/> that was updated.</returns>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// The task result contains a <see cref="DirectionCreateUpdateDto"/> that was updated.</returns>
Task<Result<DirectionDto>> Update(DirectionDto dto);
/// The task result contains a <see cref="DirectionDto"/> that was updated.</returns>
Task<Result<DirectionDto>> Update(DirectionDto dto);

Comment on lines 25 to 26
/// The task result contains a <see cref="DirectionCreateUpdateDto"/> that was created.</returns>
Task<Result<DirectionDto>> Create(DirectionDto dto);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Documentation and return type mismatch.

The documentation mentions DirectionCreateUpdateDto but the method returns Result<DirectionDto>. This inconsistency should be fixed.

-    /// The task result contains a <see cref="DirectionCreateUpdateDto"/> that was created.</returns>
+    /// The task result contains a <see cref="Result{DirectionDto}"/> that was created.</returns>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// The task result contains a <see cref="DirectionCreateUpdateDto"/> that was created.</returns>
Task<Result<DirectionDto>> Create(DirectionDto dto);
/// The task result contains a <see cref="Result{DirectionDto}"/> that was created.</returns>
Task<Result<DirectionDto>> Create(DirectionDto dto);

@@ -28,7 +28,7 @@ public InstitutionHierarchyRepository(OutOfSchoolDbContext dbContext)
/// <returns>A <see cref="Task{TResult}"/> representing the result of the asynchronous operation.</returns>
public async Task<InstitutionHierarchy> Create(InstitutionHierarchy entity, List<long> directionsIds)
{
entity.Directions = dbContext.Directions.Where(w => directionsIds.Contains(w.Id)).ToList();
entity.SubDirections = dbContext.Directions.Where(w => directionsIds.Contains(w.Id)).SelectMany(d => d.SubDirections).ToList();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Optimize the query and add validation.

The current implementation has potential performance issues and lacks validation:

  1. Consider using a more efficient query to avoid loading unnecessary data
  2. Add null/empty check for directionsIds
  3. Consider explicitly setting the entity state

Here's a suggested improvement:

+    if (directionsIds == null || !directionsIds.Any())
+    {
+        throw new ArgumentException("Direction IDs cannot be null or empty", nameof(directionsIds));
+    }
-    entity.SubDirections = dbContext.Directions.Where(w => directionsIds.Contains(w.Id)).SelectMany(d => d.SubDirections).ToList();
+    entity.SubDirections = await dbContext.SubDirections
+        .Where(sd => directionsIds.Contains(sd.DirectionId))
+        .ToListAsync();
+    dbContext.Entry(entity).State = EntityState.Added;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
entity.SubDirections = dbContext.Directions.Where(w => directionsIds.Contains(w.Id)).SelectMany(d => d.SubDirections).ToList();
if (directionsIds == null || !directionsIds.Any())
{
throw new ArgumentException("Direction IDs cannot be null or empty", nameof(directionsIds));
}
entity.SubDirections = await dbContext.SubDirections
.Where(sd => directionsIds.Contains(sd.DirectionId))
.ToListAsync();
dbContext.Entry(entity).State = EntityState.Added;

Comment on lines +51 to +53
newEntity.SubDirections.RemoveAll(x => !directionsIds.Contains(x.DirectionId));
var exceptDirectionsIds = directionsIds.Where(p => newEntity.SubDirections.TrueForAll(x => x.DirectionId != p));
newEntity.SubDirections.AddRange(dbContext.Directions.Where(w => exceptDirectionsIds.Contains(w.Id)).SelectMany(d => d.SubDirections).ToList());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Simplify the update logic and improve performance.

The current implementation has several concerns:

  1. Multiple database operations could impact performance
  2. No concurrency handling
  3. Complex and hard-to-read logic
  4. Potential memory issues with large datasets

Consider this more efficient approach:

+    if (directionsIds == null || !directionsIds.Any())
+    {
+        throw new ArgumentException("Direction IDs cannot be null or empty", nameof(directionsIds));
+    }
+
+    // Add concurrency check
+    if (entity.Version != null)
+    {
+        dbContext.Entry(newEntity).OriginalValues["Version"] = entity.Version;
+    }
+
-    newEntity.SubDirections.RemoveAll(x => !directionsIds.Contains(x.DirectionId));
-    var exceptDirectionsIds = directionsIds.Where(p => newEntity.SubDirections.TrueForAll(x => x.DirectionId != p));
-    newEntity.SubDirections.AddRange(dbContext.Directions.Where(w => exceptDirectionsIds.Contains(w.Id)).SelectMany(d => d.SubDirections).ToList());
+    // Efficiently update the SubDirections collection
+    var currentSubDirectionIds = newEntity.SubDirections.Select(sd => sd.Id).ToHashSet();
+    var newSubDirections = await dbContext.SubDirections
+        .Where(sd => directionsIds.Contains(sd.DirectionId))
+        .ToListAsync();
+    var newSubDirectionIds = newSubDirections.Select(sd => sd.Id).ToHashSet();
+
+    // Remove SubDirections that are no longer needed
+    newEntity.SubDirections.RemoveAll(sd => !newSubDirectionIds.Contains(sd.Id));
+
+    // Add new SubDirections
+    foreach (var subDirection in newSubDirections.Where(sd => !currentSubDirectionIds.Contains(sd.Id)))
+    {
+        newEntity.SubDirections.Add(subDirection);
+    }

This approach:

  1. Uses HashSet for efficient lookups
  2. Adds concurrency check
  3. Reduces database roundtrips
  4. Improves readability
  5. Adds input validation
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
newEntity.SubDirections.RemoveAll(x => !directionsIds.Contains(x.DirectionId));
var exceptDirectionsIds = directionsIds.Where(p => newEntity.SubDirections.TrueForAll(x => x.DirectionId != p));
newEntity.SubDirections.AddRange(dbContext.Directions.Where(w => exceptDirectionsIds.Contains(w.Id)).SelectMany(d => d.SubDirections).ToList());
if (directionsIds == null || !directionsIds.Any())
{
throw new ArgumentException("Direction IDs cannot be null or empty", nameof(directionsIds));
}
// Add concurrency check
if (entity.Version != null)
{
dbContext.Entry(newEntity).OriginalValues["Version"] = entity.Version;
}
// Efficiently update the SubDirections collection
var currentSubDirectionIds = newEntity.SubDirections.Select(sd => sd.Id).ToHashSet();
var newSubDirections = await dbContext.SubDirections
.Where(sd => directionsIds.Contains(sd.DirectionId))
.ToListAsync();
var newSubDirectionIds = newSubDirections.Select(sd => sd.Id).ToHashSet();
// Remove SubDirections that are no longer needed
newEntity.SubDirections.RemoveAll(sd => !newSubDirectionIds.Contains(sd.Id));
// Add new SubDirections
foreach (var subDirection in newSubDirections.Where(sd => !currentSubDirectionIds.Contains(sd.Id)))
{
newEntity.SubDirections.Add(subDirection);
}

@omaiva omaiva marked this pull request as ready for review February 12, 2025 20:21
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (6)
OutOfSchool/OutOfSchool.WebApi.Tests/Controllers/DirectionControllerTests.cs (1)

181-198: Consider verifying the error message content.

While the test properly verifies the response type and status code, it would be more robust to also assert that the error description matches the expected message.

 // Assert
 Assert.That(result, Is.TypeOf<BadRequestObjectResult>());
 Assert.That(result.StatusCode, Is.EqualTo(400));
+var error = (result.Value as Result<DirectionDto>)?.Error;
+Assert.That(error?.Description, Is.EqualTo("There is already a Direction with such a data."));
OutOfSchool/OutOfSchool.WebApi.Tests/Services/DirectionServiceTests.cs (1)

223-225: Enhance test coverage for the SubDirection relationship.

While the query has been updated to use the new SubDirection relationship, consider adding assertions to verify:

  1. The complete hierarchy (Direction -> SubDirection -> InstitutionHierarchy)
  2. The correct mapping of SubDirection properties
 // Assert
 Assert.True(result.Entities.All(d => d.Title == expectedDto.Title));
+Assert.That(result.Entities.All(d => d.SubDirections != null && d.SubDirections.Any()));
+Assert.That(result.Entities.All(d => d.SubDirections.All(s => s.InstitutionHierarchies != null && s.InstitutionHierarchies.Any())));

Also applies to: 260-262

OutOfSchool/OutOfSchool.BusinessLogic/Services/DirectionService.cs (4)

58-74: LGTM! Consider updating XML documentation.

The changes improve error handling by using the Result pattern and collecting validation errors. The logging level change to Debug is appropriate for this context.

Update the XML documentation to reflect the new return type:

/// <summary>
/// Creates new direction.
/// </summary>
/// <param name="dto">Direction entity to add.</param>
-/// <returns>A <see cref="Task{TResult}"/> representing the result of the asynchronous operation.</returns>
+/// <returns>A <see cref="Task{Result{DirectionDto}}"/> representing the result of the asynchronous operation, containing either the created direction or validation errors.</returns>

148-154: Extract magic strings and improve query readability.

Consider extracting "SubDirections" as a constant and using method chaining for better readability.

+ private const string SubDirectionsInclude = "SubDirections";

- .Get(skip: filter.From, take: filter.Size, whereExpression: predicate, orderBy: sortExpression, includeProperties: "SubDirections")
+ .Get(
+     skip: filter.From,
+     take: filter.Size,
+     whereExpression: predicate,
+     orderBy: sortExpression,
+     includeProperties: SubDirectionsInclude)

235-251: Add input validation.

Consider validating the input dto before checking for duplicates.

private List<OperationError> DirectionValidation(DirectionDto dto)
{
    var errors = new List<OperationError>();

+   if (dto == null)
+   {
+       errors.Add(new OperationError
+       {
+           Code = "400",
+           Description = localizer["Direction data cannot be null."]
+       });
+       return errors;
+   }

+   if (string.IsNullOrWhiteSpace(dto.Title))
+   {
+       errors.Add(new OperationError
+       {
+           Code = "400",
+           Description = localizer["Direction title cannot be empty."]
+       });
+   }

    if (repository.Get(whereExpression: x => x.Title == dto.Title).Any())
    {
        // ... existing duplicate check ...
    }

    return errors;
}

272-283: Extract complex predicates into separate methods.

Consider extracting the institution hierarchy predicates into separate methods for better readability and maintainability.

+ private Expression<Func<Direction, bool>> BuildInstitutionHierarchyPredicate(long institutionId)
+ {
+     return PredicateBuilder.True<Direction>()
+         .And<Direction>(d => d.SubDirections
+         .Any(s => s.InstitutionHierarchies.Any(h => h.InstitutionId == institutionId)));
+ }

  if (currentUserService.IsMinistryAdmin())
  {
      var ministryAdmin = await ministryAdminService.GetByUserId(currentUserService.UserId);
-     predicate = predicate
-         .And<Direction>(d => d.SubDirections.
-         Any(s => s.InstitutionHierarchies.Any(h => h.InstitutionId == ministryAdmin.InstitutionId)));
+     predicate = predicate.And(BuildInstitutionHierarchyPredicate(ministryAdmin.InstitutionId));
      // ... rest of the code
  }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bb39b77 and 332a734.

📒 Files selected for processing (6)
  • OutOfSchool/OutOfSchool.BusinessLogic/Services/DirectionService.cs (7 hunks)
  • OutOfSchool/OutOfSchool.BusinessLogic/Services/IDirectionService.cs (2 hunks)
  • OutOfSchool/OutOfSchool.BusinessLogic/Services/ISensitiveDirectionService.cs (1 hunks)
  • OutOfSchool/OutOfSchool.WebApi.Tests/Controllers/DirectionControllerTests.cs (5 hunks)
  • OutOfSchool/OutOfSchool.WebApi.Tests/Services/DirectionServiceTests.cs (7 hunks)
  • OutOfSchool/OutOfSchool.WebApi/Controllers/V1/DirectionController.cs (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • OutOfSchool/OutOfSchool.BusinessLogic/Services/IDirectionService.cs
  • OutOfSchool/OutOfSchool.BusinessLogic/Services/ISensitiveDirectionService.cs
  • OutOfSchool/OutOfSchool.WebApi/Controllers/V1/DirectionController.cs
🔇 Additional comments (10)
OutOfSchool/OutOfSchool.WebApi.Tests/Controllers/DirectionControllerTests.cs (4)

13-13: LGTM!

The added import is necessary to support the new Result<T> type usage in the tests.


139-149: Great improvement in error handling!

The test now correctly expects a 404 NotFound response for invalid IDs, which aligns with RESTful best practices. This is a better approach than returning null.


156-157: LGTM!

The test now properly handles the new Result<T> pattern, which provides better error handling and success state management.


215-227: LGTM!

The addition of IDs to the fake test data is necessary for proper testing of ID-based operations.

OutOfSchool/OutOfSchool.WebApi.Tests/Services/DirectionServiceTests.cs (5)

12-12: LGTM!

The addition of the BusinessLogic.Common namespace import is necessary to support the Result pattern implementation.


97-98: LGTM!

The test methods have been correctly updated to handle the Result return type:

  • Assertions now properly access the Value property
  • Failure case validation checks both the Result object and its error details

Also applies to: 103-120


163-169: Update test name to match new behavior.

The test name suggests it's expecting an exception, but the implementation checks for a null result.


198-198: LGTM!

The error message has been improved for better readability.


313-330: LGTM!

The seed data has been properly updated to reflect the new data model structure with SubDirection and its relationships.

OutOfSchool/OutOfSchool.BusinessLogic/Services/DirectionService.cs (1)

200-233: LGTM! Comprehensive error handling improvements.

The changes properly handle null input, non-existent entities, and use appropriate error codes and messages.

Comment on lines 114 to 118
catch (DbUpdateConcurrencyException ex)
{
logger.LogError($"Deleting failed. Direction with Id = {id} doesn't exist in the system.");
throw;
logger.LogError(ex, "Deleting failed. Direction with Id = {id} doesn't exist in the system.", id);
throw new DbUpdateConcurrencyException();
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve exception handling.

Re-throwing the exception loses the stack trace. Consider either:

  1. Just throwing the original exception: throw;
  2. Or wrapping it in a custom exception with more context.
-            throw new DbUpdateConcurrencyException();
+            throw;  // Preserve the original stack trace
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
catch (DbUpdateConcurrencyException ex)
{
logger.LogError($"Deleting failed. Direction with Id = {id} doesn't exist in the system.");
throw;
logger.LogError(ex, "Deleting failed. Direction with Id = {id} doesn't exist in the system.", id);
throw new DbUpdateConcurrencyException();
}
catch (DbUpdateConcurrencyException ex)
{
logger.LogError(ex, "Deleting failed. Direction with Id = {id} doesn't exist in the system.", id);
throw; // Preserve the original stack trace
}

Comment on lines 182 to 197
public async Task<DirectionDto> GetById(long id)
{
logger.LogInformation($"Getting Direction by Id started. Looking Id = {id}.");
logger.LogDebug("Getting Direction by Id started. Looking Id = {id}.", id);

var direction = await repository.GetById((int)id).ConfigureAwait(false);

if (direction == null)
{
throw new ArgumentOutOfRangeException(
nameof(id),
localizer["The id cannot be greater than number of table entities."]);
logger.LogError("Direction with Id = {id} doesn't exist in the system.", id);
return null;
}

logger.LogInformation($"Successfully got a Direction with Id = {id}.");
logger.LogDebug("Successfully got a Direction with Id = {id}.", id);

return mapper.Map<DirectionDto>(direction);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider using Result pattern for consistency.

For consistency with other methods, consider returning Result instead of null.

- public async Task<DirectionDto> GetById(long id)
+ public async Task<Result<DirectionDto>> GetById(long id)
{
    logger.LogDebug("Getting Direction by Id started. Looking Id = {id}.", id);

    var direction = await repository.GetById((int)id).ConfigureAwait(false);

    if (direction == null)
    {
        logger.LogError("Direction with Id = {id} doesn't exist in the system.", id);
-       return null;
+       return Result<DirectionDto>.Failed(new OperationError
+       {
+           Code = "404",
+           Description = $"Direction with Id = {id} does not exist."
+       });
    }

    logger.LogDebug("Successfully got a Direction with Id = {id}.", id);

-   return mapper.Map<DirectionDto>(direction);
+   return Result<DirectionDto>.Success(mapper.Map<DirectionDto>(direction));
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public async Task<DirectionDto> GetById(long id)
{
logger.LogInformation($"Getting Direction by Id started. Looking Id = {id}.");
logger.LogDebug("Getting Direction by Id started. Looking Id = {id}.", id);
var direction = await repository.GetById((int)id).ConfigureAwait(false);
if (direction == null)
{
throw new ArgumentOutOfRangeException(
nameof(id),
localizer["The id cannot be greater than number of table entities."]);
logger.LogError("Direction with Id = {id} doesn't exist in the system.", id);
return null;
}
logger.LogInformation($"Successfully got a Direction with Id = {id}.");
logger.LogDebug("Successfully got a Direction with Id = {id}.", id);
return mapper.Map<DirectionDto>(direction);
}
public async Task<Result<DirectionDto>> GetById(long id)
{
logger.LogDebug("Getting Direction by Id started. Looking Id = {id}.", id);
var direction = await repository.GetById((int)id).ConfigureAwait(false);
if (direction == null)
{
logger.LogError("Direction with Id = {id} doesn't exist in the system.", id);
return Result<DirectionDto>.Failed(new OperationError
{
Code = "404",
Description = $"Direction with Id = {id} does not exist."
});
}
logger.LogDebug("Successfully got a Direction with Id = {id}.", id);
return Result<DirectionDto>.Success(mapper.Map<DirectionDto>(direction));
}

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
OutOfSchool/OutOfSchool.BusinessLogic/Services/DirectionService.cs (3)

138-183: Add pagination parameter validation.

The method accepts pagination parameters (From, Size) but doesn't validate them. This could lead to performance issues with large page sizes.

Add validation at the beginning of the method:

 public async Task<SearchResult<DirectionDto>> GetByFilter(DirectionFilter filter, bool isAdmins)
 {
     logger.LogDebug("Getting Directions by filter started.");
 
+    if (filter.Size <= 0 || filter.Size > 100)  // Adjust max size as needed
+    {
+        filter.Size = 10;  // Default size
+    }
+
+    if (filter.From < 0)
+    {
+        filter.From = 0;
+    }
+
     var (predicate, workshopCountFilter) = await BuildPredicate(filter, isAdmins).ConfigureAwait(false);

239-255: Enhance validation rules.

The validation only checks for duplicate titles. Consider adding validations for:

  • Required properties (Title)
  • String length limits
  • Invalid characters

Add additional validations:

 private List<OperationError> DirectionValidation(DirectionDto dto)
 {
     var errors = new List<OperationError>();
 
+    if (string.IsNullOrWhiteSpace(dto.Title))
+    {
+        errors.Add(new OperationError
+        {
+            Code = "400",
+            Description = localizer["Title is required."]
+        });
+    }
+    else if (dto.Title.Length > 100)  // Adjust max length as needed
+    {
+        errors.Add(new OperationError
+        {
+            Code = "400",
+            Description = localizer["Title exceeds maximum length."]
+        });
+    }
+
     if (repository.Get(whereExpression: x => x.Title == dto.Title).Any())
     {

261-266: Optimize string search performance.

The current implementation performs case-insensitive contains check on both Direction and SubDirection titles. Consider normalizing the search term and using an indexed column for better performance.

Optimize the search:

     if (!string.IsNullOrWhiteSpace(filter.Name))
     {
+        var searchTerm = filter.Name.Trim().ToUpperInvariant();
         predicate = predicate
-            .And(direction => direction.Title.Contains(filter.Name, StringComparison.InvariantCultureIgnoreCase) ||
-            direction.SubDirections.Any(s => s.Title.Contains(filter.Name, StringComparison.InvariantCultureIgnoreCase)));
+            .And(direction => direction.NormalizedTitle.Contains(searchTerm) ||
+            direction.SubDirections.Any(s => s.NormalizedTitle.Contains(searchTerm)));
     }

Note: This assumes you'll add a normalized (uppercase) title column to both Direction and SubDirection entities, which should be maintained at insert/update time.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 332a734 and 7013411.

📒 Files selected for processing (1)
  • OutOfSchool/OutOfSchool.BusinessLogic/Services/DirectionService.cs (7 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: test (macOS-latest)
  • GitHub Check: test (windows-latest)
  • GitHub Check: SonarCloud
  • GitHub Check: test (ubuntu-latest)
🔇 Additional comments (2)
OutOfSchool/OutOfSchool.BusinessLogic/Services/DirectionService.cs (2)

114-122: Improve exception handling.

Re-throwing the exception loses the stack trace. Consider either:

  1. Just throwing the original exception: throw;
  2. Or wrapping it in a custom exception with more context.

186-201: Consider using Result pattern for consistency.

For consistency with other methods, consider returning Result instead of null.

Comment on lines +58 to 75
public async Task<Result<DirectionDto>> Create(DirectionDto dto)
{
logger.LogInformation("Direction creating was started.");
logger.LogDebug("Direction creating was started.");

var direction = mapper.Map<Direction>(dto);

DirectionValidation(dto);
var validationErrors = DirectionValidation(dto);
if (validationErrors.Any())
{
return Result<DirectionDto>.Failed(validationErrors.ToArray());
}

var newDirection = await repository.Create(direction).ConfigureAwait(false);

logger.LogInformation($"Direction with Id = {newDirection?.Id} created successfully.");
logger.LogDebug("Direction with Id = {id} created successfully.", newDirection?.Id);

return mapper.Map<DirectionDto>(newDirection);
return Result<DirectionDto>.Success(mapper.Map<DirectionDto>(newDirection));
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add null check for input parameter.

The Create method should validate that the input dto is not null before proceeding with the operation.

Add this check at the beginning of the method:

 public async Task<Result<DirectionDto>> Create(DirectionDto dto)
 {
     logger.LogDebug("Direction creating was started.");
 
+    if (dto == null)
+    {
+        logger.LogError("Creation failed. Dto is null");
+        return Result<DirectionDto>.Failed(new OperationError
+        {
+            Code = "400",
+            Description = "Direction dto cannot be null"
+        });
+    }
+
     var direction = mapper.Map<Direction>(dto);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public async Task<Result<DirectionDto>> Create(DirectionDto dto)
{
logger.LogInformation("Direction creating was started.");
logger.LogDebug("Direction creating was started.");
var direction = mapper.Map<Direction>(dto);
DirectionValidation(dto);
var validationErrors = DirectionValidation(dto);
if (validationErrors.Any())
{
return Result<DirectionDto>.Failed(validationErrors.ToArray());
}
var newDirection = await repository.Create(direction).ConfigureAwait(false);
logger.LogInformation($"Direction with Id = {newDirection?.Id} created successfully.");
logger.LogDebug("Direction with Id = {id} created successfully.", newDirection?.Id);
return mapper.Map<DirectionDto>(newDirection);
return Result<DirectionDto>.Success(mapper.Map<DirectionDto>(newDirection));
}
public async Task<Result<DirectionDto>> Create(DirectionDto dto)
{
logger.LogDebug("Direction creating was started.");
if (dto == null)
{
logger.LogError("Creation failed. Dto is null");
return Result<DirectionDto>.Failed(new OperationError
{
Code = "400",
Description = "Direction dto cannot be null"
});
}
var direction = mapper.Map<Direction>(dto);
var validationErrors = DirectionValidation(dto);
if (validationErrors.Any())
{
return Result<DirectionDto>.Failed(validationErrors.ToArray());
}
var newDirection = await repository.Create(direction).ConfigureAwait(false);
logger.LogDebug("Direction with Id = {id} created successfully.", newDirection?.Id);
return Result<DirectionDto>.Success(mapper.Map<DirectionDto>(newDirection));
}

Comment on lines +204 to 237
public async Task<Result<DirectionDto>> Update(DirectionDto dto)
{
logger.LogInformation($"Updating Direction with Id = {dto?.Id} started.");
logger.LogDebug("Updating Direction with Id = {id} started.", dto?.Id);

ArgumentNullException.ThrowIfNull(dto);
if (dto == null)
{
logger.LogError("Updating failed. Dto is null");
return Result<DirectionDto>.Failed(new OperationError
{
Code = "400",
Description = $"Dto is null.",
});
}


var direction = await repository.GetById(dto.Id).ConfigureAwait(false);

if (direction is null)
{
logger.LogError($"Updating failed. Direction with Id = {dto?.Id} doesn't exist in the system.");
throw new DbUpdateConcurrencyException($"Updating failed. Direction with Id = {dto?.Id} doesn't exist in the system.");
logger.LogError("Updating failed. Direction with Id = {id} doesn't exist in the system.", dto.Id);
return Result<DirectionDto>.Failed(new OperationError
{
Code = "404",
Description = $"Direction with Id = {dto.Id} does not exist.",
});
}

mapper.Map(dto, direction);
direction = await repository.Update(direction).ConfigureAwait(false);

logger.LogInformation($"Direction with Id = {direction?.Id} updated succesfully.");
logger.LogDebug("Direction with Id = {id} updated succesfully.", direction?.Id);

return mapper.Map<DirectionDto>(direction);
return Result<DirectionDto>.Success(mapper.Map<DirectionDto>(direction));
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add property validation before update.

The method validates that dto is not null but doesn't validate its properties. Consider reusing DirectionValidation method.

Add validation before updating:

     if (direction is null)
     {
         logger.LogError("Updating failed. Direction with Id = {id} doesn't exist in the system.", dto.Id);
         return Result<DirectionDto>.Failed(new OperationError
         {
             Code = "404",
             Description = $"Direction with Id = {dto.Id} does not exist.",
         });
     }

+    var validationErrors = DirectionValidation(dto);
+    if (validationErrors.Any())
+    {
+        return Result<DirectionDto>.Failed(validationErrors.ToArray());
+    }
+
     mapper.Map(dto, direction);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public async Task<Result<DirectionDto>> Update(DirectionDto dto)
{
logger.LogInformation($"Updating Direction with Id = {dto?.Id} started.");
logger.LogDebug("Updating Direction with Id = {id} started.", dto?.Id);
ArgumentNullException.ThrowIfNull(dto);
if (dto == null)
{
logger.LogError("Updating failed. Dto is null");
return Result<DirectionDto>.Failed(new OperationError
{
Code = "400",
Description = $"Dto is null.",
});
}
var direction = await repository.GetById(dto.Id).ConfigureAwait(false);
if (direction is null)
{
logger.LogError($"Updating failed. Direction with Id = {dto?.Id} doesn't exist in the system.");
throw new DbUpdateConcurrencyException($"Updating failed. Direction with Id = {dto?.Id} doesn't exist in the system.");
logger.LogError("Updating failed. Direction with Id = {id} doesn't exist in the system.", dto.Id);
return Result<DirectionDto>.Failed(new OperationError
{
Code = "404",
Description = $"Direction with Id = {dto.Id} does not exist.",
});
}
mapper.Map(dto, direction);
direction = await repository.Update(direction).ConfigureAwait(false);
logger.LogInformation($"Direction with Id = {direction?.Id} updated succesfully.");
logger.LogDebug("Direction with Id = {id} updated succesfully.", direction?.Id);
return mapper.Map<DirectionDto>(direction);
return Result<DirectionDto>.Success(mapper.Map<DirectionDto>(direction));
}
public async Task<Result<DirectionDto>> Update(DirectionDto dto)
{
logger.LogDebug("Updating Direction with Id = {id} started.", dto?.Id);
if (dto == null)
{
logger.LogError("Updating failed. Dto is null");
return Result<DirectionDto>.Failed(new OperationError
{
Code = "400",
Description = $"Dto is null.",
});
}
var direction = await repository.GetById(dto.Id).ConfigureAwait(false);
if (direction is null)
{
logger.LogError("Updating failed. Direction with Id = {id} doesn't exist in the system.", dto.Id);
return Result<DirectionDto>.Failed(new OperationError
{
Code = "404",
Description = $"Direction with Id = {dto.Id} does not exist.",
});
}
var validationErrors = DirectionValidation(dto);
if (validationErrors.Any())
{
return Result<DirectionDto>.Failed(validationErrors.ToArray());
}
mapper.Map(dto, direction);
direction = await repository.Update(direction).ConfigureAwait(false);
logger.LogDebug("Direction with Id = {id} updated succesfully.", direction?.Id);
return Result<DirectionDto>.Success(mapper.Map<DirectionDto>(direction));
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add Sub-Direction layer for user-facing Directions
1 participant