diff --git a/src/APITemplate/Api/Program.cs b/src/APITemplate/Api/Program.cs
index 8efa39ce..36ebeff7 100644
--- a/src/APITemplate/Api/Program.cs
+++ b/src/APITemplate/Api/Program.cs
@@ -10,7 +10,7 @@
using JasperFx;
using JasperFx.Resources;
using ProductCatalog;
-using ProductCatalog.Features.CreateProducts;
+using ProductCatalog.Features.Product.CreateProducts;
using ProductCatalog.Handlers;
using Reviews;
using Reviews.Features;
diff --git a/src/Modules/ProductCatalog/Features/Category/CategoriesController.cs b/src/Modules/ProductCatalog/Features/Category/CategoriesController.cs
new file mode 100644
index 00000000..b4a17c1a
--- /dev/null
+++ b/src/Modules/ProductCatalog/Features/Category/CategoriesController.cs
@@ -0,0 +1,9 @@
+using Asp.Versioning;
+using Microsoft.AspNetCore.Mvc;
+using SharedKernel.Contracts.Api;
+using Wolverine;
+
+namespace ProductCatalog.Features.Category;
+
+[ApiVersion(1.0)]
+public sealed partial class CategoriesController(IMessageBus bus) : ApiControllerBase { }
diff --git a/src/Modules/ProductCatalog/Features/CreateCategories/CreateCategoriesController.cs b/src/Modules/ProductCatalog/Features/Category/CreateCategories/CategoriesController.CreateCategories.cs
similarity index 76%
rename from src/Modules/ProductCatalog/Features/CreateCategories/CreateCategoriesController.cs
rename to src/Modules/ProductCatalog/Features/Category/CreateCategories/CategoriesController.CreateCategories.cs
index 2cd7ad15..a21c7e68 100644
--- a/src/Modules/ProductCatalog/Features/CreateCategories/CreateCategoriesController.cs
+++ b/src/Modules/ProductCatalog/Features/Category/CreateCategories/CategoriesController.CreateCategories.cs
@@ -1,14 +1,12 @@
-using Asp.Versioning;
using ErrorOr;
using Microsoft.AspNetCore.Mvc;
+using ProductCatalog.Features.Category.CreateCategories;
using SharedKernel.Contracts.Api;
using SharedKernel.Contracts.Security;
-using Wolverine;
-namespace ProductCatalog.Features.CreateCategories;
+namespace ProductCatalog.Features.Category;
-[ApiVersion(1.0)]
-public sealed class CreateCategoriesController(IMessageBus bus) : ApiControllerBase
+public sealed partial class CategoriesController
{
/// Creates multiple categories in a single batch operation.
[HttpPost]
diff --git a/src/Modules/ProductCatalog/Features/CreateCategories/CreateCategoriesCommand.cs b/src/Modules/ProductCatalog/Features/Category/CreateCategories/CreateCategoriesCommand.cs
similarity index 97%
rename from src/Modules/ProductCatalog/Features/CreateCategories/CreateCategoriesCommand.cs
rename to src/Modules/ProductCatalog/Features/Category/CreateCategories/CreateCategoriesCommand.cs
index 08c4b237..ecf7d9a7 100644
--- a/src/Modules/ProductCatalog/Features/CreateCategories/CreateCategoriesCommand.cs
+++ b/src/Modules/ProductCatalog/Features/Category/CreateCategories/CreateCategoriesCommand.cs
@@ -5,7 +5,7 @@
using Wolverine;
using CategoryEntity = ProductCatalog.Entities.Category;
-namespace ProductCatalog.Features.CreateCategories;
+namespace ProductCatalog.Features.Category.CreateCategories;
/// Creates multiple categories in a single batch operation.
public sealed record CreateCategoriesCommand(CreateCategoriesRequest Request);
diff --git a/src/Modules/ProductCatalog/Features/CreateCategories/CreateCategoriesRequest.cs b/src/Modules/ProductCatalog/Features/Category/CreateCategories/CreateCategoriesRequest.cs
similarity index 87%
rename from src/Modules/ProductCatalog/Features/CreateCategories/CreateCategoriesRequest.cs
rename to src/Modules/ProductCatalog/Features/Category/CreateCategories/CreateCategoriesRequest.cs
index 36e380b9..11d90d6b 100644
--- a/src/Modules/ProductCatalog/Features/CreateCategories/CreateCategoriesRequest.cs
+++ b/src/Modules/ProductCatalog/Features/Category/CreateCategories/CreateCategoriesRequest.cs
@@ -1,6 +1,6 @@
using System.ComponentModel.DataAnnotations;
-namespace ProductCatalog.Features.CreateCategories;
+namespace ProductCatalog.Features.Category.CreateCategories;
///
/// Carries a list of category items to be created in a single batch operation; accepts between 1 and 100 items.
diff --git a/src/Modules/ProductCatalog/Features/CreateCategories/CreateCategoryRequest.cs b/src/Modules/ProductCatalog/Features/Category/CreateCategories/CreateCategoryRequest.cs
similarity index 87%
rename from src/Modules/ProductCatalog/Features/CreateCategories/CreateCategoryRequest.cs
rename to src/Modules/ProductCatalog/Features/Category/CreateCategories/CreateCategoryRequest.cs
index d6c7634f..55171cec 100644
--- a/src/Modules/ProductCatalog/Features/CreateCategories/CreateCategoryRequest.cs
+++ b/src/Modules/ProductCatalog/Features/Category/CreateCategories/CreateCategoryRequest.cs
@@ -1,7 +1,7 @@
using System.ComponentModel.DataAnnotations;
using SharedKernel.Application.Validation;
-namespace ProductCatalog.Features.CreateCategories;
+namespace ProductCatalog.Features.Category.CreateCategories;
///
/// Payload for creating a new category, carrying the name and optional description.
diff --git a/src/Modules/ProductCatalog/Features/CreateCategories/CreateCategoryRequestValidator.cs b/src/Modules/ProductCatalog/Features/Category/CreateCategories/CreateCategoryRequestValidator.cs
similarity index 82%
rename from src/Modules/ProductCatalog/Features/CreateCategories/CreateCategoryRequestValidator.cs
rename to src/Modules/ProductCatalog/Features/Category/CreateCategories/CreateCategoryRequestValidator.cs
index 749b42b9..1319e439 100644
--- a/src/Modules/ProductCatalog/Features/CreateCategories/CreateCategoryRequestValidator.cs
+++ b/src/Modules/ProductCatalog/Features/Category/CreateCategories/CreateCategoryRequestValidator.cs
@@ -1,6 +1,6 @@
using SharedKernel.Application.Validation;
-namespace ProductCatalog.Features.CreateCategories;
+namespace ProductCatalog.Features.Category.CreateCategories;
///
/// FluentValidation validator for that enforces data-annotation constraints.
diff --git a/src/Modules/ProductCatalog/Features/DeleteCategories/DeleteCategoriesController.cs b/src/Modules/ProductCatalog/Features/Category/DeleteCategories/CategoriesController.DeleteCategories.cs
similarity index 76%
rename from src/Modules/ProductCatalog/Features/DeleteCategories/DeleteCategoriesController.cs
rename to src/Modules/ProductCatalog/Features/Category/DeleteCategories/CategoriesController.DeleteCategories.cs
index 2fe0dcde..72782df3 100644
--- a/src/Modules/ProductCatalog/Features/DeleteCategories/DeleteCategoriesController.cs
+++ b/src/Modules/ProductCatalog/Features/Category/DeleteCategories/CategoriesController.DeleteCategories.cs
@@ -1,14 +1,12 @@
-using Asp.Versioning;
using ErrorOr;
using Microsoft.AspNetCore.Mvc;
+using ProductCatalog.Features.Category.DeleteCategories;
using SharedKernel.Contracts.Api;
using SharedKernel.Contracts.Security;
-using Wolverine;
-namespace ProductCatalog.Features.DeleteCategories;
+namespace ProductCatalog.Features.Category;
-[ApiVersion(1.0)]
-public sealed class DeleteCategoriesController(IMessageBus bus) : ApiControllerBase
+public sealed partial class CategoriesController
{
/// Soft-deletes multiple categories in a single batch operation.
[HttpDelete]
diff --git a/src/Modules/ProductCatalog/Features/DeleteCategories/DeleteCategoriesCommand.cs b/src/Modules/ProductCatalog/Features/Category/DeleteCategories/DeleteCategoriesCommand.cs
similarity index 97%
rename from src/Modules/ProductCatalog/Features/DeleteCategories/DeleteCategoriesCommand.cs
rename to src/Modules/ProductCatalog/Features/Category/DeleteCategories/DeleteCategoriesCommand.cs
index cecbdb4e..26c72b8c 100644
--- a/src/Modules/ProductCatalog/Features/DeleteCategories/DeleteCategoriesCommand.cs
+++ b/src/Modules/ProductCatalog/Features/Category/DeleteCategories/DeleteCategoriesCommand.cs
@@ -2,7 +2,7 @@
using ProductCatalog;
using Wolverine;
-namespace ProductCatalog.Features.DeleteCategories;
+namespace ProductCatalog.Features.Category.DeleteCategories;
/// Soft-deletes multiple categories in a single batch operation.
public sealed record DeleteCategoriesCommand(BatchDeleteRequest Request);
diff --git a/src/Modules/ProductCatalog/Features/GetCategories/GetCategoriesController.cs b/src/Modules/ProductCatalog/Features/Category/GetCategories/CategoriesController.GetCategories.cs
similarity index 80%
rename from src/Modules/ProductCatalog/Features/GetCategories/GetCategoriesController.cs
rename to src/Modules/ProductCatalog/Features/Category/GetCategories/CategoriesController.GetCategories.cs
index 31a260c9..ca6127e9 100644
--- a/src/Modules/ProductCatalog/Features/GetCategories/GetCategoriesController.cs
+++ b/src/Modules/ProductCatalog/Features/Category/GetCategories/CategoriesController.GetCategories.cs
@@ -1,15 +1,13 @@
-using Asp.Versioning;
using ErrorOr;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.OutputCaching;
+using ProductCatalog.Features.Category.GetCategories;
using SharedKernel.Contracts.Api;
using SharedKernel.Contracts.Security;
-using Wolverine;
-namespace ProductCatalog.Features.GetCategories;
+namespace ProductCatalog.Features.Category;
-[ApiVersion(1.0)]
-public sealed class GetCategoriesController(IMessageBus bus) : ApiControllerBase
+public sealed partial class CategoriesController
{
/// Returns a paginated, filterable list of categories from the output cache.
[HttpGet]
diff --git a/src/Modules/ProductCatalog/Features/GetCategories/CategoryFilter.cs b/src/Modules/ProductCatalog/Features/Category/GetCategories/CategoryFilter.cs
similarity index 89%
rename from src/Modules/ProductCatalog/Features/GetCategories/CategoryFilter.cs
rename to src/Modules/ProductCatalog/Features/Category/GetCategories/CategoryFilter.cs
index 9c3f6d4d..ce308095 100644
--- a/src/Modules/ProductCatalog/Features/GetCategories/CategoryFilter.cs
+++ b/src/Modules/ProductCatalog/Features/Category/GetCategories/CategoryFilter.cs
@@ -1,7 +1,7 @@
using SharedKernel.Application.Contracts;
using SharedKernel.Application.DTOs;
-namespace ProductCatalog.Features.GetCategories;
+namespace ProductCatalog.Features.Category.GetCategories;
///
/// Filter parameters for querying categories, supporting full-text search, sorting, and pagination.
diff --git a/src/Modules/ProductCatalog/Features/GetCategories/CategoryFilterCriteria.cs b/src/Modules/ProductCatalog/Features/Category/GetCategories/CategoryFilterCriteria.cs
similarity index 95%
rename from src/Modules/ProductCatalog/Features/GetCategories/CategoryFilterCriteria.cs
rename to src/Modules/ProductCatalog/Features/Category/GetCategories/CategoryFilterCriteria.cs
index 0e5cdadd..5513cf05 100644
--- a/src/Modules/ProductCatalog/Features/GetCategories/CategoryFilterCriteria.cs
+++ b/src/Modules/ProductCatalog/Features/Category/GetCategories/CategoryFilterCriteria.cs
@@ -3,7 +3,7 @@
using Microsoft.EntityFrameworkCore;
using CategoryEntity = ProductCatalog.Entities.Category;
-namespace ProductCatalog.Features.GetCategories;
+namespace ProductCatalog.Features.Category.GetCategories;
///
/// Extension methods that apply search criteria to an Ardalis specification builder.
diff --git a/src/Modules/ProductCatalog/Features/GetCategories/CategoryFilterValidator.cs b/src/Modules/ProductCatalog/Features/Category/GetCategories/CategoryFilterValidator.cs
similarity index 89%
rename from src/Modules/ProductCatalog/Features/GetCategories/CategoryFilterValidator.cs
rename to src/Modules/ProductCatalog/Features/Category/GetCategories/CategoryFilterValidator.cs
index e84c4323..ca829d1f 100644
--- a/src/Modules/ProductCatalog/Features/GetCategories/CategoryFilterValidator.cs
+++ b/src/Modules/ProductCatalog/Features/Category/GetCategories/CategoryFilterValidator.cs
@@ -1,7 +1,7 @@
using SharedKernel.Application.Validation;
using FluentValidation;
-namespace ProductCatalog.Features.GetCategories;
+namespace ProductCatalog.Features.Category.GetCategories;
///
/// FluentValidation validator for .
diff --git a/src/Modules/ProductCatalog/Features/GetCategories/CategorySpecification.cs b/src/Modules/ProductCatalog/Features/Category/GetCategories/CategorySpecification.cs
similarity index 92%
rename from src/Modules/ProductCatalog/Features/GetCategories/CategorySpecification.cs
rename to src/Modules/ProductCatalog/Features/Category/GetCategories/CategorySpecification.cs
index c059feac..0a515f99 100644
--- a/src/Modules/ProductCatalog/Features/GetCategories/CategorySpecification.cs
+++ b/src/Modules/ProductCatalog/Features/Category/GetCategories/CategorySpecification.cs
@@ -1,7 +1,7 @@
using Ardalis.Specification;
using CategoryEntity = ProductCatalog.Entities.Category;
-namespace ProductCatalog.Features.GetCategories;
+namespace ProductCatalog.Features.Category.GetCategories;
///
/// Ardalis specification for querying a filtered and sorted list of categories projected to .
diff --git a/src/Modules/ProductCatalog/Features/GetCategories/GetCategoriesQuery.cs b/src/Modules/ProductCatalog/Features/Category/GetCategories/GetCategoriesQuery.cs
similarity index 92%
rename from src/Modules/ProductCatalog/Features/GetCategories/GetCategoriesQuery.cs
rename to src/Modules/ProductCatalog/Features/Category/GetCategories/GetCategoriesQuery.cs
index 391c7d80..53ebe472 100644
--- a/src/Modules/ProductCatalog/Features/GetCategories/GetCategoriesQuery.cs
+++ b/src/Modules/ProductCatalog/Features/Category/GetCategories/GetCategoriesQuery.cs
@@ -1,6 +1,6 @@
using ErrorOr;
-namespace ProductCatalog.Features.GetCategories;
+namespace ProductCatalog.Features.Category.GetCategories;
/// Returns a paginated, filtered, and sorted list of categories.
public sealed record GetCategoriesQuery(CategoryFilter Filter);
diff --git a/src/Modules/ProductCatalog/Features/GetCategoryById/GetCategoryByIdController.cs b/src/Modules/ProductCatalog/Features/Category/GetCategoryById/CategoriesController.GetCategoryById.cs
similarity index 78%
rename from src/Modules/ProductCatalog/Features/GetCategoryById/GetCategoryByIdController.cs
rename to src/Modules/ProductCatalog/Features/Category/GetCategoryById/CategoriesController.GetCategoryById.cs
index 6d4aabbd..d4384503 100644
--- a/src/Modules/ProductCatalog/Features/GetCategoryById/GetCategoryByIdController.cs
+++ b/src/Modules/ProductCatalog/Features/Category/GetCategoryById/CategoriesController.GetCategoryById.cs
@@ -1,15 +1,13 @@
-using Asp.Versioning;
using ErrorOr;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.OutputCaching;
+using ProductCatalog.Features.Category.GetCategoryById;
using SharedKernel.Contracts.Api;
using SharedKernel.Contracts.Security;
-using Wolverine;
-namespace ProductCatalog.Features.GetCategoryById;
+namespace ProductCatalog.Features.Category;
-[ApiVersion(1.0)]
-public sealed class GetCategoryByIdController(IMessageBus bus) : ApiControllerBase
+public sealed partial class CategoriesController
{
/// Returns a single category by its identifier, or 404 if not found.
[HttpGet("{id:guid}")]
diff --git a/src/Modules/ProductCatalog/Features/GetCategoryById/CategoryByIdSpecification.cs b/src/Modules/ProductCatalog/Features/Category/GetCategoryById/CategoryByIdSpecification.cs
similarity index 91%
rename from src/Modules/ProductCatalog/Features/GetCategoryById/CategoryByIdSpecification.cs
rename to src/Modules/ProductCatalog/Features/Category/GetCategoryById/CategoryByIdSpecification.cs
index 56882605..fc97b7c1 100644
--- a/src/Modules/ProductCatalog/Features/GetCategoryById/CategoryByIdSpecification.cs
+++ b/src/Modules/ProductCatalog/Features/Category/GetCategoryById/CategoryByIdSpecification.cs
@@ -1,7 +1,7 @@
using Ardalis.Specification;
using CategoryEntity = ProductCatalog.Entities.Category;
-namespace ProductCatalog.Features.GetCategoryById;
+namespace ProductCatalog.Features.Category.GetCategoryById;
///
/// Ardalis specification that fetches a single category by its identifier, projected directly to .
diff --git a/src/Modules/ProductCatalog/Features/GetCategoryById/GetCategoryByIdQuery.cs b/src/Modules/ProductCatalog/Features/Category/GetCategoryById/GetCategoryByIdQuery.cs
similarity index 93%
rename from src/Modules/ProductCatalog/Features/GetCategoryById/GetCategoryByIdQuery.cs
rename to src/Modules/ProductCatalog/Features/Category/GetCategoryById/GetCategoryByIdQuery.cs
index 548c7c95..6b887d58 100644
--- a/src/Modules/ProductCatalog/Features/GetCategoryById/GetCategoryByIdQuery.cs
+++ b/src/Modules/ProductCatalog/Features/Category/GetCategoryById/GetCategoryByIdQuery.cs
@@ -1,7 +1,7 @@
using ErrorOr;
using ProductCatalog.Interfaces;
-namespace ProductCatalog.Features.GetCategoryById;
+namespace ProductCatalog.Features.Category.GetCategoryById;
/// Returns a single category by its unique identifier, or if not found.
public sealed record GetCategoryByIdQuery(Guid Id) : IHasId;
diff --git a/src/Modules/ProductCatalog/Features/GetCategoryStats/GetCategoryStatsController.cs b/src/Modules/ProductCatalog/Features/Category/GetCategoryStats/CategoriesController.GetCategoryStats.cs
similarity index 66%
rename from src/Modules/ProductCatalog/Features/GetCategoryStats/GetCategoryStatsController.cs
rename to src/Modules/ProductCatalog/Features/Category/GetCategoryStats/CategoriesController.GetCategoryStats.cs
index f25824d3..2decc1ee 100644
--- a/src/Modules/ProductCatalog/Features/GetCategoryStats/GetCategoryStatsController.cs
+++ b/src/Modules/ProductCatalog/Features/Category/GetCategoryStats/CategoriesController.GetCategoryStats.cs
@@ -1,19 +1,17 @@
-using Asp.Versioning;
using ErrorOr;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.OutputCaching;
+using ProductCatalog.Features.Category.GetCategoryStats;
using SharedKernel.Contracts.Api;
using SharedKernel.Contracts.Security;
-using Wolverine;
-namespace ProductCatalog.Features.GetCategoryStats;
+namespace ProductCatalog.Features.Category;
-[ApiVersion(1.0)]
-public sealed class GetCategoryStatsController(IMessageBus bus) : ApiControllerBase
+public sealed partial class CategoriesController
{
///
- /// Returns aggregated statistics for a category by calling the
- /// get_product_category_stats(p_category_id) stored procedure via EF Core FromSql.
+ /// Returns aggregated statistics for a category via
+ /// get_product_category_stats(p_category_id) (EF Core FromSql).
///
[HttpGet("{id:guid}/stats")]
[RequirePermission(Permission.Categories.Read)]
diff --git a/src/Modules/ProductCatalog/Features/GetCategoryStats/GetCategoryStatsQuery.cs b/src/Modules/ProductCatalog/Features/Category/GetCategoryStats/GetCategoryStatsQuery.cs
similarity index 94%
rename from src/Modules/ProductCatalog/Features/GetCategoryStats/GetCategoryStatsQuery.cs
rename to src/Modules/ProductCatalog/Features/Category/GetCategoryStats/GetCategoryStatsQuery.cs
index a768f173..bd269e46 100644
--- a/src/Modules/ProductCatalog/Features/GetCategoryStats/GetCategoryStatsQuery.cs
+++ b/src/Modules/ProductCatalog/Features/Category/GetCategoryStats/GetCategoryStatsQuery.cs
@@ -2,7 +2,7 @@
using ProductCatalog.Interfaces;
using ProductCategoryStatsEntity = ProductCatalog.Entities.ProductCategoryStats;
-namespace ProductCatalog.Features.GetCategoryStats;
+namespace ProductCatalog.Features.Category.GetCategoryStats;
/// Returns aggregated statistics for a category by its identifier, or if not found.
public sealed record GetCategoryStatsQuery(Guid Id) : IHasId;
diff --git a/src/Modules/ProductCatalog/Features/GetCategoryStats/ProductCategoryStatsResponse.cs b/src/Modules/ProductCatalog/Features/Category/GetCategoryStats/ProductCategoryStatsResponse.cs
similarity index 83%
rename from src/Modules/ProductCatalog/Features/GetCategoryStats/ProductCategoryStatsResponse.cs
rename to src/Modules/ProductCatalog/Features/Category/GetCategoryStats/ProductCategoryStatsResponse.cs
index 1a000353..ab691a60 100644
--- a/src/Modules/ProductCatalog/Features/GetCategoryStats/ProductCategoryStatsResponse.cs
+++ b/src/Modules/ProductCatalog/Features/Category/GetCategoryStats/ProductCategoryStatsResponse.cs
@@ -1,4 +1,4 @@
-namespace ProductCatalog.Features.GetCategoryStats;
+namespace ProductCatalog.Features.Category.GetCategoryStats;
///
/// Aggregated statistics for a single category, including product count, average price, and total review count.
diff --git a/src/Modules/ProductCatalog/Features/Shared/CategoriesByIdsSpecification.cs b/src/Modules/ProductCatalog/Features/Category/Shared/CategoriesByIdsSpecification.cs
similarity index 90%
rename from src/Modules/ProductCatalog/Features/Shared/CategoriesByIdsSpecification.cs
rename to src/Modules/ProductCatalog/Features/Category/Shared/CategoriesByIdsSpecification.cs
index 2aa5466e..d538a393 100644
--- a/src/Modules/ProductCatalog/Features/Shared/CategoriesByIdsSpecification.cs
+++ b/src/Modules/ProductCatalog/Features/Category/Shared/CategoriesByIdsSpecification.cs
@@ -1,7 +1,7 @@
using Ardalis.Specification;
using CategoryEntity = ProductCatalog.Entities.Category;
-namespace ProductCatalog.Features.Shared;
+namespace ProductCatalog.Features.Category.Shared;
///
/// Ardalis specification that loads multiple categories by their IDs, used for batch update and delete operations.
diff --git a/src/Modules/ProductCatalog/Features/Shared/CategoryMappings.cs b/src/Modules/ProductCatalog/Features/Category/Shared/CategoryMappings.cs
similarity index 96%
rename from src/Modules/ProductCatalog/Features/Shared/CategoryMappings.cs
rename to src/Modules/ProductCatalog/Features/Category/Shared/CategoryMappings.cs
index 136986d7..6f584ba6 100644
--- a/src/Modules/ProductCatalog/Features/Shared/CategoryMappings.cs
+++ b/src/Modules/ProductCatalog/Features/Category/Shared/CategoryMappings.cs
@@ -1,7 +1,7 @@
using System.Linq.Expressions;
using CategoryEntity = ProductCatalog.Entities.Category;
-namespace ProductCatalog.Features.Shared;
+namespace ProductCatalog.Features.Category.Shared;
///
/// Provides mapping utilities between category domain entities and their response DTOs.
diff --git a/src/Modules/ProductCatalog/Features/Shared/CategoryResponse.cs b/src/Modules/ProductCatalog/Features/Category/Shared/CategoryResponse.cs
similarity index 83%
rename from src/Modules/ProductCatalog/Features/Shared/CategoryResponse.cs
rename to src/Modules/ProductCatalog/Features/Category/Shared/CategoryResponse.cs
index f9e9b4c1..ce2b7fc5 100644
--- a/src/Modules/ProductCatalog/Features/Shared/CategoryResponse.cs
+++ b/src/Modules/ProductCatalog/Features/Category/Shared/CategoryResponse.cs
@@ -1,4 +1,4 @@
-namespace ProductCatalog.Features.Shared;
+namespace ProductCatalog.Features.Category.Shared;
///
/// Read model returned by category queries, containing the public-facing representation of a category.
diff --git a/src/Modules/ProductCatalog/Features/Shared/CategorySortFields.cs b/src/Modules/ProductCatalog/Features/Category/Shared/CategorySortFields.cs
similarity index 94%
rename from src/Modules/ProductCatalog/Features/Shared/CategorySortFields.cs
rename to src/Modules/ProductCatalog/Features/Category/Shared/CategorySortFields.cs
index 78b05347..9904ad76 100644
--- a/src/Modules/ProductCatalog/Features/Shared/CategorySortFields.cs
+++ b/src/Modules/ProductCatalog/Features/Category/Shared/CategorySortFields.cs
@@ -1,7 +1,7 @@
using SharedKernel.Application.Sorting;
using CategoryEntity = ProductCatalog.Entities.Category;
-namespace ProductCatalog.Features.Shared;
+namespace ProductCatalog.Features.Category.Shared;
///
/// Defines the allowed sort fields for category queries and maps them to entity expressions.
diff --git a/src/Modules/ProductCatalog/Features/UpdateCategories/UpdateCategoriesController.cs b/src/Modules/ProductCatalog/Features/Category/UpdateCategories/CategoriesController.UpdateCategories.cs
similarity index 76%
rename from src/Modules/ProductCatalog/Features/UpdateCategories/UpdateCategoriesController.cs
rename to src/Modules/ProductCatalog/Features/Category/UpdateCategories/CategoriesController.UpdateCategories.cs
index 05fd0c72..7d704ba6 100644
--- a/src/Modules/ProductCatalog/Features/UpdateCategories/UpdateCategoriesController.cs
+++ b/src/Modules/ProductCatalog/Features/Category/UpdateCategories/CategoriesController.UpdateCategories.cs
@@ -1,14 +1,12 @@
-using Asp.Versioning;
using ErrorOr;
using Microsoft.AspNetCore.Mvc;
+using ProductCatalog.Features.Category.UpdateCategories;
using SharedKernel.Contracts.Api;
using SharedKernel.Contracts.Security;
-using Wolverine;
-namespace ProductCatalog.Features.UpdateCategories;
+namespace ProductCatalog.Features.Category;
-[ApiVersion(1.0)]
-public sealed class UpdateCategoriesController(IMessageBus bus) : ApiControllerBase
+public sealed partial class CategoriesController
{
/// Updates multiple categories in a single batch operation.
[HttpPut]
diff --git a/src/Modules/ProductCatalog/Features/UpdateCategories/UpdateCategoriesCommand.cs b/src/Modules/ProductCatalog/Features/Category/UpdateCategories/UpdateCategoriesCommand.cs
similarity index 98%
rename from src/Modules/ProductCatalog/Features/UpdateCategories/UpdateCategoriesCommand.cs
rename to src/Modules/ProductCatalog/Features/Category/UpdateCategories/UpdateCategoriesCommand.cs
index 7d2655fb..a1575546 100644
--- a/src/Modules/ProductCatalog/Features/UpdateCategories/UpdateCategoriesCommand.cs
+++ b/src/Modules/ProductCatalog/Features/Category/UpdateCategories/UpdateCategoriesCommand.cs
@@ -5,7 +5,7 @@
using SharedKernel.Contracts.Events;
using Wolverine;
-namespace ProductCatalog.Features.UpdateCategories;
+namespace ProductCatalog.Features.Category.UpdateCategories;
/// Updates multiple categories in a single batch operation.
public sealed record UpdateCategoriesCommand(UpdateCategoriesRequest Request);
diff --git a/src/Modules/ProductCatalog/Features/UpdateCategories/UpdateCategoriesRequest.cs b/src/Modules/ProductCatalog/Features/Category/UpdateCategories/UpdateCategoriesRequest.cs
similarity index 93%
rename from src/Modules/ProductCatalog/Features/UpdateCategories/UpdateCategoriesRequest.cs
rename to src/Modules/ProductCatalog/Features/Category/UpdateCategories/UpdateCategoriesRequest.cs
index 69f708e8..c2377843 100644
--- a/src/Modules/ProductCatalog/Features/UpdateCategories/UpdateCategoriesRequest.cs
+++ b/src/Modules/ProductCatalog/Features/Category/UpdateCategories/UpdateCategoriesRequest.cs
@@ -1,7 +1,7 @@
using System.ComponentModel.DataAnnotations;
using SharedKernel.Application.Validation;
-namespace ProductCatalog.Features.UpdateCategories;
+namespace ProductCatalog.Features.Category.UpdateCategories;
///
/// Carries a list of category items to be updated in a single batch operation; accepts between 1 and 100 items.
diff --git a/src/Modules/ProductCatalog/Features/UpdateCategories/UpdateCategoryItemValidator.cs b/src/Modules/ProductCatalog/Features/Category/UpdateCategories/UpdateCategoryItemValidator.cs
similarity index 82%
rename from src/Modules/ProductCatalog/Features/UpdateCategories/UpdateCategoryItemValidator.cs
rename to src/Modules/ProductCatalog/Features/Category/UpdateCategories/UpdateCategoryItemValidator.cs
index 6a3fe4bc..891f1a90 100644
--- a/src/Modules/ProductCatalog/Features/UpdateCategories/UpdateCategoryItemValidator.cs
+++ b/src/Modules/ProductCatalog/Features/Category/UpdateCategories/UpdateCategoryItemValidator.cs
@@ -1,6 +1,6 @@
using SharedKernel.Application.Validation;
-namespace ProductCatalog.Features.UpdateCategories;
+namespace ProductCatalog.Features.Category.UpdateCategories;
///
/// FluentValidation validator for that enforces data-annotation constraints.
diff --git a/src/Modules/ProductCatalog/Features/UpdateCategories/UpdateCategoryRequest.cs b/src/Modules/ProductCatalog/Features/Category/UpdateCategories/UpdateCategoryRequest.cs
similarity index 75%
rename from src/Modules/ProductCatalog/Features/UpdateCategories/UpdateCategoryRequest.cs
rename to src/Modules/ProductCatalog/Features/Category/UpdateCategories/UpdateCategoryRequest.cs
index 896b4647..3de5fb31 100644
--- a/src/Modules/ProductCatalog/Features/UpdateCategories/UpdateCategoryRequest.cs
+++ b/src/Modules/ProductCatalog/Features/Category/UpdateCategories/UpdateCategoryRequest.cs
@@ -1,4 +1,4 @@
-namespace ProductCatalog.Features.UpdateCategories;
+namespace ProductCatalog.Features.Category.UpdateCategories;
///
/// Payload for updating an existing category's name and optional description.
diff --git a/src/Modules/ProductCatalog/Features/CreateProducts/CreateProductRequest.cs b/src/Modules/ProductCatalog/Features/Product/CreateProducts/CreateProductRequest.cs
similarity index 91%
rename from src/Modules/ProductCatalog/Features/CreateProducts/CreateProductRequest.cs
rename to src/Modules/ProductCatalog/Features/Product/CreateProducts/CreateProductRequest.cs
index 70772563..2272fa44 100644
--- a/src/Modules/ProductCatalog/Features/CreateProducts/CreateProductRequest.cs
+++ b/src/Modules/ProductCatalog/Features/Product/CreateProducts/CreateProductRequest.cs
@@ -1,6 +1,6 @@
using System.ComponentModel.DataAnnotations;
-namespace ProductCatalog.Features.CreateProducts;
+namespace ProductCatalog.Features.Product.CreateProducts;
///
/// Carries the data required to create a new product, including validation constraints enforced via data annotations.
diff --git a/src/Modules/ProductCatalog/Features/CreateProducts/CreateProductRequestValidator.cs b/src/Modules/ProductCatalog/Features/Product/CreateProducts/CreateProductRequestValidator.cs
similarity index 82%
rename from src/Modules/ProductCatalog/Features/CreateProducts/CreateProductRequestValidator.cs
rename to src/Modules/ProductCatalog/Features/Product/CreateProducts/CreateProductRequestValidator.cs
index 2a22a678..ecd72d17 100644
--- a/src/Modules/ProductCatalog/Features/CreateProducts/CreateProductRequestValidator.cs
+++ b/src/Modules/ProductCatalog/Features/Product/CreateProducts/CreateProductRequestValidator.cs
@@ -1,4 +1,4 @@
-namespace ProductCatalog.Features.CreateProducts;
+namespace ProductCatalog.Features.Product.CreateProducts;
///
/// FluentValidation validator for , inheriting all rules from .
diff --git a/src/Modules/ProductCatalog/Features/CreateProducts/CreateProductsCommand.cs b/src/Modules/ProductCatalog/Features/Product/CreateProducts/CreateProductsCommand.cs
similarity index 98%
rename from src/Modules/ProductCatalog/Features/CreateProducts/CreateProductsCommand.cs
rename to src/Modules/ProductCatalog/Features/Product/CreateProducts/CreateProductsCommand.cs
index 8d88083d..8a91a72a 100644
--- a/src/Modules/ProductCatalog/Features/CreateProducts/CreateProductsCommand.cs
+++ b/src/Modules/ProductCatalog/Features/Product/CreateProducts/CreateProductsCommand.cs
@@ -8,7 +8,7 @@
using ProductEntity = ProductCatalog.Entities.Product;
using ProductRepositoryContract = ProductCatalog.Interfaces.IProductRepository;
-namespace ProductCatalog.Features.CreateProducts;
+namespace ProductCatalog.Features.Product.CreateProducts;
/// Creates multiple products in a single batch operation.
public sealed record CreateProductsCommand(CreateProductsRequest Request);
diff --git a/src/Modules/ProductCatalog/Features/CreateProducts/CreateProductsRequest.cs b/src/Modules/ProductCatalog/Features/Product/CreateProducts/CreateProductsRequest.cs
similarity index 87%
rename from src/Modules/ProductCatalog/Features/CreateProducts/CreateProductsRequest.cs
rename to src/Modules/ProductCatalog/Features/Product/CreateProducts/CreateProductsRequest.cs
index f0677c9d..2f0d2abc 100644
--- a/src/Modules/ProductCatalog/Features/CreateProducts/CreateProductsRequest.cs
+++ b/src/Modules/ProductCatalog/Features/Product/CreateProducts/CreateProductsRequest.cs
@@ -1,6 +1,6 @@
using System.ComponentModel.DataAnnotations;
-namespace ProductCatalog.Features.CreateProducts;
+namespace ProductCatalog.Features.Product.CreateProducts;
///
/// Carries a list of product items to be created in a single batch operation; accepts between 1 and 100 items.
diff --git a/src/Modules/ProductCatalog/Features/CreateProducts/CreateProductsController.cs b/src/Modules/ProductCatalog/Features/Product/CreateProducts/ProductsController.CreateProducts.cs
similarity index 76%
rename from src/Modules/ProductCatalog/Features/CreateProducts/CreateProductsController.cs
rename to src/Modules/ProductCatalog/Features/Product/CreateProducts/ProductsController.CreateProducts.cs
index 1a6fa330..3101dd86 100644
--- a/src/Modules/ProductCatalog/Features/CreateProducts/CreateProductsController.cs
+++ b/src/Modules/ProductCatalog/Features/Product/CreateProducts/ProductsController.CreateProducts.cs
@@ -1,14 +1,12 @@
-using Asp.Versioning;
using ErrorOr;
using Microsoft.AspNetCore.Mvc;
+using ProductCatalog.Features.Product.CreateProducts;
using SharedKernel.Contracts.Api;
using SharedKernel.Contracts.Security;
-using Wolverine;
-namespace ProductCatalog.Features.CreateProducts;
+namespace ProductCatalog.Features.Product;
-[ApiVersion(1.0)]
-public sealed class CreateProductsController(IMessageBus bus) : ApiControllerBase
+public sealed partial class ProductsController
{
/// Creates multiple products in a single batch operation.
[HttpPost]
diff --git a/src/Modules/ProductCatalog/Features/DeleteProducts/DeleteProductsCommand.cs b/src/Modules/ProductCatalog/Features/Product/DeleteProducts/DeleteProductsCommand.cs
similarity index 98%
rename from src/Modules/ProductCatalog/Features/DeleteProducts/DeleteProductsCommand.cs
rename to src/Modules/ProductCatalog/Features/Product/DeleteProducts/DeleteProductsCommand.cs
index 5ce0c4a4..1bd06e74 100644
--- a/src/Modules/ProductCatalog/Features/DeleteProducts/DeleteProductsCommand.cs
+++ b/src/Modules/ProductCatalog/Features/Product/DeleteProducts/DeleteProductsCommand.cs
@@ -3,7 +3,7 @@
using Wolverine;
using ProductRepositoryContract = ProductCatalog.Interfaces.IProductRepository;
-namespace ProductCatalog.Features.DeleteProducts;
+namespace ProductCatalog.Features.Product.DeleteProducts;
/// Soft-deletes multiple products and their associated data links in a single batch operation.
public sealed record DeleteProductsCommand(BatchDeleteRequest Request);
diff --git a/src/Modules/ProductCatalog/Features/DeleteProducts/DeleteProductsController.cs b/src/Modules/ProductCatalog/Features/Product/DeleteProducts/ProductsController.DeleteProducts.cs
similarity index 76%
rename from src/Modules/ProductCatalog/Features/DeleteProducts/DeleteProductsController.cs
rename to src/Modules/ProductCatalog/Features/Product/DeleteProducts/ProductsController.DeleteProducts.cs
index eb76845a..af922a55 100644
--- a/src/Modules/ProductCatalog/Features/DeleteProducts/DeleteProductsController.cs
+++ b/src/Modules/ProductCatalog/Features/Product/DeleteProducts/ProductsController.DeleteProducts.cs
@@ -1,14 +1,12 @@
-using Asp.Versioning;
using ErrorOr;
using Microsoft.AspNetCore.Mvc;
+using ProductCatalog.Features.Product.DeleteProducts;
using SharedKernel.Contracts.Api;
using SharedKernel.Contracts.Security;
-using Wolverine;
-namespace ProductCatalog.Features.DeleteProducts;
+namespace ProductCatalog.Features.Product;
-[ApiVersion(1.0)]
-public sealed class DeleteProductsController(IMessageBus bus) : ApiControllerBase
+public sealed partial class ProductsController
{
/// Soft-deletes multiple products in a single batch operation.
[HttpDelete]
diff --git a/src/Modules/ProductCatalog/Features/GetProductById/GetProductByIdQuery.cs b/src/Modules/ProductCatalog/Features/Product/GetProductById/GetProductByIdQuery.cs
similarity index 93%
rename from src/Modules/ProductCatalog/Features/GetProductById/GetProductByIdQuery.cs
rename to src/Modules/ProductCatalog/Features/Product/GetProductById/GetProductByIdQuery.cs
index 1cd0037f..58ce283f 100644
--- a/src/Modules/ProductCatalog/Features/GetProductById/GetProductByIdQuery.cs
+++ b/src/Modules/ProductCatalog/Features/Product/GetProductById/GetProductByIdQuery.cs
@@ -1,7 +1,7 @@
using ErrorOr;
using ProductRepositoryContract = ProductCatalog.Interfaces.IProductRepository;
-namespace ProductCatalog.Features.GetProductById;
+namespace ProductCatalog.Features.Product.GetProductById;
/// Retrieves a single product by its unique identifier.
public sealed record GetProductByIdQuery(Guid Id) : IHasId;
diff --git a/src/Modules/ProductCatalog/Features/GetProductById/ProductByIdSpecification.cs b/src/Modules/ProductCatalog/Features/Product/GetProductById/ProductByIdSpecification.cs
similarity index 89%
rename from src/Modules/ProductCatalog/Features/GetProductById/ProductByIdSpecification.cs
rename to src/Modules/ProductCatalog/Features/Product/GetProductById/ProductByIdSpecification.cs
index 8cd63658..0f4b4e27 100644
--- a/src/Modules/ProductCatalog/Features/GetProductById/ProductByIdSpecification.cs
+++ b/src/Modules/ProductCatalog/Features/Product/GetProductById/ProductByIdSpecification.cs
@@ -1,7 +1,7 @@
using Ardalis.Specification;
using ProductEntity = ProductCatalog.Entities.Product;
-namespace ProductCatalog.Features.GetProductById;
+namespace ProductCatalog.Features.Product.GetProductById;
///
/// Ardalis specification that fetches a single product by its ID and projects it directly to a DTO.
diff --git a/src/Modules/ProductCatalog/Features/GetProductById/ProductByIdWithLinksSpecification.cs b/src/Modules/ProductCatalog/Features/Product/GetProductById/ProductByIdWithLinksSpecification.cs
similarity index 90%
rename from src/Modules/ProductCatalog/Features/GetProductById/ProductByIdWithLinksSpecification.cs
rename to src/Modules/ProductCatalog/Features/Product/GetProductById/ProductByIdWithLinksSpecification.cs
index 53d47975..37294066 100644
--- a/src/Modules/ProductCatalog/Features/GetProductById/ProductByIdWithLinksSpecification.cs
+++ b/src/Modules/ProductCatalog/Features/Product/GetProductById/ProductByIdWithLinksSpecification.cs
@@ -1,7 +1,7 @@
using Ardalis.Specification;
using ProductEntity = ProductCatalog.Entities.Product;
-namespace ProductCatalog.Features.GetProductById;
+namespace ProductCatalog.Features.Product.GetProductById;
///
/// Ardalis specification that loads a product by ID and eagerly includes its ProductDataLinks collection, used when link synchronisation or deletion is required.
diff --git a/src/Modules/ProductCatalog/Features/GetProductById/GetProductByIdController.cs b/src/Modules/ProductCatalog/Features/Product/GetProductById/ProductsController.GetProductById.cs
similarity index 78%
rename from src/Modules/ProductCatalog/Features/GetProductById/GetProductByIdController.cs
rename to src/Modules/ProductCatalog/Features/Product/GetProductById/ProductsController.GetProductById.cs
index 0b1524b2..6ff10e46 100644
--- a/src/Modules/ProductCatalog/Features/GetProductById/GetProductByIdController.cs
+++ b/src/Modules/ProductCatalog/Features/Product/GetProductById/ProductsController.GetProductById.cs
@@ -1,15 +1,13 @@
-using Asp.Versioning;
using ErrorOr;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.OutputCaching;
+using ProductCatalog.Features.Product.GetProductById;
using SharedKernel.Contracts.Api;
using SharedKernel.Contracts.Security;
-using Wolverine;
-namespace ProductCatalog.Features.GetProductById;
+namespace ProductCatalog.Features.Product;
-[ApiVersion(1.0)]
-public sealed class GetProductByIdController(IMessageBus bus) : ApiControllerBase
+public sealed partial class ProductsController
{
/// Returns a single product by its identifier, or 404 if not found.
[HttpGet("{id:guid}")]
diff --git a/src/Modules/ProductCatalog/Features/GetProducts/GetProductsQuery.cs b/src/Modules/ProductCatalog/Features/Product/GetProducts/GetProductsQuery.cs
similarity index 95%
rename from src/Modules/ProductCatalog/Features/GetProducts/GetProductsQuery.cs
rename to src/Modules/ProductCatalog/Features/Product/GetProducts/GetProductsQuery.cs
index 7fb3bb13..aa43055d 100644
--- a/src/Modules/ProductCatalog/Features/GetProducts/GetProductsQuery.cs
+++ b/src/Modules/ProductCatalog/Features/Product/GetProducts/GetProductsQuery.cs
@@ -1,7 +1,7 @@
using ErrorOr;
using ProductRepositoryContract = ProductCatalog.Interfaces.IProductRepository;
-namespace ProductCatalog.Features.GetProducts;
+namespace ProductCatalog.Features.Product.GetProducts;
/// Retrieves a filtered, sorted, and paged list of products together with search facets.
public sealed record GetProductsQuery(ProductFilter Filter);
diff --git a/src/Modules/ProductCatalog/Features/GetProducts/ProductFilter.cs b/src/Modules/ProductCatalog/Features/Product/GetProducts/ProductFilter.cs
similarity index 93%
rename from src/Modules/ProductCatalog/Features/GetProducts/ProductFilter.cs
rename to src/Modules/ProductCatalog/Features/Product/GetProducts/ProductFilter.cs
index d578fdc6..4b1d84d6 100644
--- a/src/Modules/ProductCatalog/Features/GetProducts/ProductFilter.cs
+++ b/src/Modules/ProductCatalog/Features/Product/GetProducts/ProductFilter.cs
@@ -1,7 +1,7 @@
using SharedKernel.Application.Contracts;
using SharedKernel.Application.DTOs;
-namespace ProductCatalog.Features.GetProducts;
+namespace ProductCatalog.Features.Product.GetProducts;
///
/// Encapsulates all criteria available for querying and paging the product list, including text search, price range, date range, category filtering, and sorting.
diff --git a/src/Modules/ProductCatalog/Features/GetProducts/ProductFilterCriteria.cs b/src/Modules/ProductCatalog/Features/Product/GetProducts/ProductFilterCriteria.cs
similarity index 98%
rename from src/Modules/ProductCatalog/Features/GetProducts/ProductFilterCriteria.cs
rename to src/Modules/ProductCatalog/Features/Product/GetProducts/ProductFilterCriteria.cs
index aae9d801..e9e7944b 100644
--- a/src/Modules/ProductCatalog/Features/GetProducts/ProductFilterCriteria.cs
+++ b/src/Modules/ProductCatalog/Features/Product/GetProducts/ProductFilterCriteria.cs
@@ -3,7 +3,7 @@
using Microsoft.EntityFrameworkCore;
using ProductEntity = ProductCatalog.Entities.Product;
-namespace ProductCatalog.Features.GetProducts;
+namespace ProductCatalog.Features.Product.GetProducts;
///
/// Internal helper that extends with product-specific filter predicates, centralising all WHERE-clause logic for reuse across multiple specifications.
diff --git a/src/Modules/ProductCatalog/Features/GetProducts/ProductFilterValidator.cs b/src/Modules/ProductCatalog/Features/Product/GetProducts/ProductFilterValidator.cs
similarity index 96%
rename from src/Modules/ProductCatalog/Features/GetProducts/ProductFilterValidator.cs
rename to src/Modules/ProductCatalog/Features/Product/GetProducts/ProductFilterValidator.cs
index 8870d9ca..590b1ff4 100644
--- a/src/Modules/ProductCatalog/Features/GetProducts/ProductFilterValidator.cs
+++ b/src/Modules/ProductCatalog/Features/Product/GetProducts/ProductFilterValidator.cs
@@ -1,7 +1,7 @@
using SharedKernel.Application.Validation;
using FluentValidation;
-namespace ProductCatalog.Features.GetProducts;
+namespace ProductCatalog.Features.Product.GetProducts;
///
/// FluentValidation validator for ; composes pagination, date-range, sortable-field, and price-range rules including cross-field MinPrice/MaxPrice consistency.
diff --git a/src/Modules/ProductCatalog/Features/GetProducts/ProductSpecification.cs b/src/Modules/ProductCatalog/Features/Product/GetProducts/ProductSpecification.cs
similarity index 91%
rename from src/Modules/ProductCatalog/Features/GetProducts/ProductSpecification.cs
rename to src/Modules/ProductCatalog/Features/Product/GetProducts/ProductSpecification.cs
index 202e4824..d1ee5f70 100644
--- a/src/Modules/ProductCatalog/Features/GetProducts/ProductSpecification.cs
+++ b/src/Modules/ProductCatalog/Features/Product/GetProducts/ProductSpecification.cs
@@ -1,7 +1,7 @@
using Ardalis.Specification;
using ProductEntity = ProductCatalog.Entities.Product;
-namespace ProductCatalog.Features.GetProducts;
+namespace ProductCatalog.Features.Product.GetProducts;
///
/// Ardalis specification that applies the full product filter, sorting, and projection to produce a list.
diff --git a/src/Modules/ProductCatalog/Features/GetProducts/GetProductsController.cs b/src/Modules/ProductCatalog/Features/Product/GetProducts/ProductsController.GetProducts.cs
similarity index 80%
rename from src/Modules/ProductCatalog/Features/GetProducts/GetProductsController.cs
rename to src/Modules/ProductCatalog/Features/Product/GetProducts/ProductsController.GetProducts.cs
index 4ce3f977..8692678a 100644
--- a/src/Modules/ProductCatalog/Features/GetProducts/GetProductsController.cs
+++ b/src/Modules/ProductCatalog/Features/Product/GetProducts/ProductsController.GetProducts.cs
@@ -1,15 +1,13 @@
-using Asp.Versioning;
using ErrorOr;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.OutputCaching;
+using ProductCatalog.Features.Product.GetProducts;
using SharedKernel.Contracts.Api;
using SharedKernel.Contracts.Security;
-using Wolverine;
-namespace ProductCatalog.Features.GetProducts;
+namespace ProductCatalog.Features.Product;
-[ApiVersion(1.0)]
-public sealed class GetProductsController(IMessageBus bus) : ApiControllerBase
+public sealed partial class ProductsController
{
/// Returns a filtered, paginated product list including search facets.
[HttpGet]
diff --git a/src/Modules/ProductCatalog/Features/IdempotentCreate/IdempotentCreateController.cs b/src/Modules/ProductCatalog/Features/Product/IdempotentCreate/IdempotentController.Create.cs
similarity index 58%
rename from src/Modules/ProductCatalog/Features/IdempotentCreate/IdempotentCreateController.cs
rename to src/Modules/ProductCatalog/Features/Product/IdempotentCreate/IdempotentController.Create.cs
index ff4916c2..24ef3706 100644
--- a/src/Modules/ProductCatalog/Features/IdempotentCreate/IdempotentCreateController.cs
+++ b/src/Modules/ProductCatalog/Features/Product/IdempotentCreate/IdempotentController.Create.cs
@@ -1,20 +1,19 @@
-using Asp.Versioning;
using ErrorOr;
using Microsoft.AspNetCore.Mvc;
+using ProductCatalog.Features.Shared.Routing;
using SharedKernel.Contracts.Api;
using SharedKernel.Contracts.Api.Filters.Idempotency;
using SharedKernel.Contracts.Security;
using Wolverine;
-namespace ProductCatalog.Features.IdempotentCreate;
+namespace ProductCatalog.Features.Product.IdempotentCreate;
-[ApiVersion(1.0)]
-///
-/// Presentation-layer controller that demonstrates idempotent POST semantics using the
-/// action filter to detect and short-circuit duplicate requests.
-///
-public sealed class IdempotentCreateController(IMessageBus bus) : ApiControllerBase
+public sealed partial class IdempotentController
{
+ ///
+ /// Demonstrates idempotent POST semantics using the
+ /// filter for duplicate requests.
+ ///
[HttpPost]
[Idempotent]
[RequirePermission(Permission.Examples.Create)]
@@ -29,6 +28,9 @@ CancellationToken ct
if (result.IsError)
return result.ToActionResult(this);
- return Ok(result.Value);
+ return Created(
+ $"/api/v{this.GetApiVersion()}/{ProductCatalogRouteTemplates.IdempotentPathSegment}",
+ result.Value
+ );
}
}
diff --git a/src/Modules/ProductCatalog/Features/Product/IdempotentCreate/IdempotentController.cs b/src/Modules/ProductCatalog/Features/Product/IdempotentCreate/IdempotentController.cs
new file mode 100644
index 00000000..7eb9445d
--- /dev/null
+++ b/src/Modules/ProductCatalog/Features/Product/IdempotentCreate/IdempotentController.cs
@@ -0,0 +1,9 @@
+using Asp.Versioning;
+using Microsoft.AspNetCore.Mvc;
+using SharedKernel.Contracts.Api;
+using Wolverine;
+
+namespace ProductCatalog.Features.Product.IdempotentCreate;
+
+[ApiVersion(1.0)]
+public sealed partial class IdempotentController(IMessageBus bus) : ApiControllerBase { }
diff --git a/src/Modules/ProductCatalog/Features/IdempotentCreate/IdempotentCreateCommand.cs b/src/Modules/ProductCatalog/Features/Product/IdempotentCreate/IdempotentCreateCommand.cs
similarity index 95%
rename from src/Modules/ProductCatalog/Features/IdempotentCreate/IdempotentCreateCommand.cs
rename to src/Modules/ProductCatalog/Features/Product/IdempotentCreate/IdempotentCreateCommand.cs
index 7f7c0009..2f52f1f5 100644
--- a/src/Modules/ProductCatalog/Features/IdempotentCreate/IdempotentCreateCommand.cs
+++ b/src/Modules/ProductCatalog/Features/Product/IdempotentCreate/IdempotentCreateCommand.cs
@@ -4,7 +4,7 @@
using IProductRepository = ProductCatalog.Interfaces.IProductRepository;
using ProductEntity = ProductCatalog.Entities.Product;
-namespace ProductCatalog.Features.IdempotentCreate;
+namespace ProductCatalog.Features.Product.IdempotentCreate;
public sealed record IdempotentCreateCommand(IdempotentCreateRequest Request);
diff --git a/src/Modules/ProductCatalog/Features/IdempotentCreate/IdempotentCreateRequest.cs b/src/Modules/ProductCatalog/Features/Product/IdempotentCreate/IdempotentCreateRequest.cs
similarity index 85%
rename from src/Modules/ProductCatalog/Features/IdempotentCreate/IdempotentCreateRequest.cs
rename to src/Modules/ProductCatalog/Features/Product/IdempotentCreate/IdempotentCreateRequest.cs
index e537ea82..b7f0393e 100644
--- a/src/Modules/ProductCatalog/Features/IdempotentCreate/IdempotentCreateRequest.cs
+++ b/src/Modules/ProductCatalog/Features/Product/IdempotentCreate/IdempotentCreateRequest.cs
@@ -1,7 +1,7 @@
using System.ComponentModel.DataAnnotations;
using SharedKernel.Application.Validation;
-namespace ProductCatalog.Features.IdempotentCreate;
+namespace ProductCatalog.Features.Product.IdempotentCreate;
///
/// Carries the data for an idempotent resource creation request; demonstrates safe-retry semantics at the API layer.
diff --git a/src/Modules/ProductCatalog/Features/IdempotentCreate/IdempotentCreateRequestValidator.cs b/src/Modules/ProductCatalog/Features/Product/IdempotentCreate/IdempotentCreateRequestValidator.cs
similarity index 85%
rename from src/Modules/ProductCatalog/Features/IdempotentCreate/IdempotentCreateRequestValidator.cs
rename to src/Modules/ProductCatalog/Features/Product/IdempotentCreate/IdempotentCreateRequestValidator.cs
index 77e97967..231fc227 100644
--- a/src/Modules/ProductCatalog/Features/IdempotentCreate/IdempotentCreateRequestValidator.cs
+++ b/src/Modules/ProductCatalog/Features/Product/IdempotentCreate/IdempotentCreateRequestValidator.cs
@@ -1,6 +1,6 @@
using SharedKernel.Application.Validation;
-namespace ProductCatalog.Features.IdempotentCreate;
+namespace ProductCatalog.Features.Product.IdempotentCreate;
///
/// FluentValidation validator for that enforces
diff --git a/src/Modules/ProductCatalog/Features/IdempotentCreate/IdempotentCreateResponse.cs b/src/Modules/ProductCatalog/Features/Product/IdempotentCreate/IdempotentCreateResponse.cs
similarity index 83%
rename from src/Modules/ProductCatalog/Features/IdempotentCreate/IdempotentCreateResponse.cs
rename to src/Modules/ProductCatalog/Features/Product/IdempotentCreate/IdempotentCreateResponse.cs
index 2b477d51..e9a83e5c 100644
--- a/src/Modules/ProductCatalog/Features/IdempotentCreate/IdempotentCreateResponse.cs
+++ b/src/Modules/ProductCatalog/Features/Product/IdempotentCreate/IdempotentCreateResponse.cs
@@ -1,6 +1,6 @@
using SharedKernel.Domain.Entities.Contracts;
-namespace ProductCatalog.Features.IdempotentCreate;
+namespace ProductCatalog.Features.Product.IdempotentCreate;
///
/// Represents the persisted resource returned after a successful idempotent create operation.
diff --git a/src/Modules/ProductCatalog/Features/PatchProduct/PatchProductController.cs b/src/Modules/ProductCatalog/Features/Product/PatchProduct/PatchController.PatchProduct.cs
similarity index 74%
rename from src/Modules/ProductCatalog/Features/PatchProduct/PatchProductController.cs
rename to src/Modules/ProductCatalog/Features/Product/PatchProduct/PatchController.PatchProduct.cs
index 8480808e..c0fc88d8 100644
--- a/src/Modules/ProductCatalog/Features/PatchProduct/PatchProductController.cs
+++ b/src/Modules/ProductCatalog/Features/Product/PatchProduct/PatchController.PatchProduct.cs
@@ -1,4 +1,3 @@
-using Asp.Versioning;
using ErrorOr;
using Microsoft.AspNetCore.Mvc;
using SharedKernel.Contracts.Api;
@@ -6,13 +5,12 @@
using SystemTextJsonPatch;
using Wolverine;
-namespace ProductCatalog.Features.PatchProduct;
+namespace ProductCatalog.Features.Product.PatchProduct;
-[ApiVersion(1.0)]
-public sealed class PatchProductController(IMessageBus bus) : ApiControllerBase
+public sealed partial class PatchController
{
[HttpPatch("products/{id:guid}")]
- [RequirePermission(Permission.Examples.Update)]
+ [RequirePermission(Permission.Products.Update)]
public async Task> PatchProduct(
Guid id,
[FromBody] JsonPatchDocument patchDocument,
diff --git a/src/Modules/ProductCatalog/Features/Product/PatchProduct/PatchController.cs b/src/Modules/ProductCatalog/Features/Product/PatchProduct/PatchController.cs
new file mode 100644
index 00000000..b07059c6
--- /dev/null
+++ b/src/Modules/ProductCatalog/Features/Product/PatchProduct/PatchController.cs
@@ -0,0 +1,9 @@
+using Asp.Versioning;
+using Microsoft.AspNetCore.Mvc;
+using SharedKernel.Contracts.Api;
+using Wolverine;
+
+namespace ProductCatalog.Features.Product.PatchProduct;
+
+[ApiVersion(1.0)]
+public sealed partial class PatchController(IMessageBus bus) : ApiControllerBase { }
diff --git a/src/Modules/ProductCatalog/Features/PatchProduct/PatchProductCommand.cs b/src/Modules/ProductCatalog/Features/Product/PatchProduct/PatchProductCommand.cs
similarity index 97%
rename from src/Modules/ProductCatalog/Features/PatchProduct/PatchProductCommand.cs
rename to src/Modules/ProductCatalog/Features/Product/PatchProduct/PatchProductCommand.cs
index 04bd84e9..8c031e82 100644
--- a/src/Modules/ProductCatalog/Features/PatchProduct/PatchProductCommand.cs
+++ b/src/Modules/ProductCatalog/Features/Product/PatchProduct/PatchProductCommand.cs
@@ -6,7 +6,7 @@
using Wolverine;
using IProductRepository = ProductCatalog.Interfaces.IProductRepository;
-namespace ProductCatalog.Features.PatchProduct;
+namespace ProductCatalog.Features.Product.PatchProduct;
public sealed record PatchProductCommand(
Guid Id,
diff --git a/src/Modules/ProductCatalog/Features/PatchProduct/PatchableProductDto.cs b/src/Modules/ProductCatalog/Features/Product/PatchProduct/PatchableProductDto.cs
similarity index 92%
rename from src/Modules/ProductCatalog/Features/PatchProduct/PatchableProductDto.cs
rename to src/Modules/ProductCatalog/Features/Product/PatchProduct/PatchableProductDto.cs
index f1fd4bca..ba45bee0 100644
--- a/src/Modules/ProductCatalog/Features/PatchProduct/PatchableProductDto.cs
+++ b/src/Modules/ProductCatalog/Features/Product/PatchProduct/PatchableProductDto.cs
@@ -1,6 +1,6 @@
using System.ComponentModel.DataAnnotations;
-namespace ProductCatalog.Features.PatchProduct;
+namespace ProductCatalog.Features.Product.PatchProduct;
///
/// Mutable DTO used as the patch target for JSON Patch operations on a product; declared as a class
diff --git a/src/Modules/ProductCatalog/Features/PatchProduct/PatchableProductDtoValidator.cs b/src/Modules/ProductCatalog/Features/Product/PatchProduct/PatchableProductDtoValidator.cs
similarity index 90%
rename from src/Modules/ProductCatalog/Features/PatchProduct/PatchableProductDtoValidator.cs
rename to src/Modules/ProductCatalog/Features/Product/PatchProduct/PatchableProductDtoValidator.cs
index ca5ea96a..72af01ad 100644
--- a/src/Modules/ProductCatalog/Features/PatchProduct/PatchableProductDtoValidator.cs
+++ b/src/Modules/ProductCatalog/Features/Product/PatchProduct/PatchableProductDtoValidator.cs
@@ -1,7 +1,7 @@
using FluentValidation;
using SharedKernel.Application.Validation;
-namespace ProductCatalog.Features.PatchProduct;
+namespace ProductCatalog.Features.Product.PatchProduct;
///
/// FluentValidation validator for the post-patch state;
diff --git a/src/Modules/ProductCatalog/Features/Product/ProductsController.cs b/src/Modules/ProductCatalog/Features/Product/ProductsController.cs
new file mode 100644
index 00000000..b2d35c43
--- /dev/null
+++ b/src/Modules/ProductCatalog/Features/Product/ProductsController.cs
@@ -0,0 +1,9 @@
+using Asp.Versioning;
+using Microsoft.AspNetCore.Mvc;
+using SharedKernel.Contracts.Api;
+using Wolverine;
+
+namespace ProductCatalog.Features.Product;
+
+[ApiVersion(1.0)]
+public sealed partial class ProductsController(IMessageBus bus) : ApiControllerBase { }
diff --git a/src/Modules/ProductCatalog/Features/Shared/IProductRequest.cs b/src/Modules/ProductCatalog/Features/Product/Shared/IProductRequest.cs
similarity index 89%
rename from src/Modules/ProductCatalog/Features/Shared/IProductRequest.cs
rename to src/Modules/ProductCatalog/Features/Product/Shared/IProductRequest.cs
index 5e039fca..74270e62 100644
--- a/src/Modules/ProductCatalog/Features/Shared/IProductRequest.cs
+++ b/src/Modules/ProductCatalog/Features/Product/Shared/IProductRequest.cs
@@ -1,4 +1,4 @@
-namespace ProductCatalog.Features.Shared;
+namespace ProductCatalog.Features.Product.Shared;
///
/// Shared contract for create and update product command requests, enabling reuse of
diff --git a/src/Modules/ProductCatalog/Features/Shared/ProductCategoryFacetSpecification.cs b/src/Modules/ProductCatalog/Features/Product/Shared/ProductCategoryFacetSpecification.cs
similarity index 85%
rename from src/Modules/ProductCatalog/Features/Shared/ProductCategoryFacetSpecification.cs
rename to src/Modules/ProductCatalog/Features/Product/Shared/ProductCategoryFacetSpecification.cs
index a702e707..b0707bda 100644
--- a/src/Modules/ProductCatalog/Features/Shared/ProductCategoryFacetSpecification.cs
+++ b/src/Modules/ProductCatalog/Features/Product/Shared/ProductCategoryFacetSpecification.cs
@@ -1,8 +1,8 @@
using Ardalis.Specification;
-using ProductCatalog.Features.GetProducts;
+using ProductCatalog.Features.Product.GetProducts;
using ProductEntity = ProductCatalog.Entities.Product;
-namespace ProductCatalog.Features.Shared;
+namespace ProductCatalog.Features.Product.Shared;
///
/// Ardalis specification used for the category facet query; applies all filter criteria except category-ID filtering so that counts reflect the full category distribution.
diff --git a/src/Modules/ProductCatalog/Features/Shared/ProductCategoryFacetValue.cs b/src/Modules/ProductCatalog/Features/Product/Shared/ProductCategoryFacetValue.cs
similarity index 84%
rename from src/Modules/ProductCatalog/Features/Shared/ProductCategoryFacetValue.cs
rename to src/Modules/ProductCatalog/Features/Product/Shared/ProductCategoryFacetValue.cs
index 3193416e..c6bd2264 100644
--- a/src/Modules/ProductCatalog/Features/Shared/ProductCategoryFacetValue.cs
+++ b/src/Modules/ProductCatalog/Features/Product/Shared/ProductCategoryFacetValue.cs
@@ -1,4 +1,4 @@
-namespace ProductCatalog.Features.Shared;
+namespace ProductCatalog.Features.Product.Shared;
///
/// Represents a single category bucket in the product search facets, containing the category identity and the number of matching products.
diff --git a/src/Modules/ProductCatalog/Features/Shared/ProductMappings.cs b/src/Modules/ProductCatalog/Features/Product/Shared/ProductMappings.cs
similarity index 96%
rename from src/Modules/ProductCatalog/Features/Shared/ProductMappings.cs
rename to src/Modules/ProductCatalog/Features/Product/Shared/ProductMappings.cs
index a23cd538..d6b35079 100644
--- a/src/Modules/ProductCatalog/Features/Shared/ProductMappings.cs
+++ b/src/Modules/ProductCatalog/Features/Product/Shared/ProductMappings.cs
@@ -1,7 +1,7 @@
using System.Linq.Expressions;
using ProductEntity = ProductCatalog.Entities.Product;
-namespace ProductCatalog.Features.Shared;
+namespace ProductCatalog.Features.Product.Shared;
///
/// Provides EF Core-compatible projection expressions and in-memory mapping helpers for converting Product domain entities to DTOs.
diff --git a/src/Modules/ProductCatalog/Features/Shared/ProductPriceFacetBucketResponse.cs b/src/Modules/ProductCatalog/Features/Product/Shared/ProductPriceFacetBucketResponse.cs
similarity index 85%
rename from src/Modules/ProductCatalog/Features/Shared/ProductPriceFacetBucketResponse.cs
rename to src/Modules/ProductCatalog/Features/Product/Shared/ProductPriceFacetBucketResponse.cs
index eff961fd..d3c41ed8 100644
--- a/src/Modules/ProductCatalog/Features/Shared/ProductPriceFacetBucketResponse.cs
+++ b/src/Modules/ProductCatalog/Features/Product/Shared/ProductPriceFacetBucketResponse.cs
@@ -1,4 +1,4 @@
-namespace ProductCatalog.Features.Shared;
+namespace ProductCatalog.Features.Product.Shared;
///
/// Represents a single price-range bucket in the product search facets, with a human-readable label and the count of matching products.
diff --git a/src/Modules/ProductCatalog/Features/Shared/ProductPriceFacetSpecification.cs b/src/Modules/ProductCatalog/Features/Product/Shared/ProductPriceFacetSpecification.cs
similarity index 85%
rename from src/Modules/ProductCatalog/Features/Shared/ProductPriceFacetSpecification.cs
rename to src/Modules/ProductCatalog/Features/Product/Shared/ProductPriceFacetSpecification.cs
index 02e185e2..95e8c5f1 100644
--- a/src/Modules/ProductCatalog/Features/Shared/ProductPriceFacetSpecification.cs
+++ b/src/Modules/ProductCatalog/Features/Product/Shared/ProductPriceFacetSpecification.cs
@@ -1,8 +1,8 @@
using Ardalis.Specification;
-using ProductCatalog.Features.GetProducts;
+using ProductCatalog.Features.Product.GetProducts;
using ProductEntity = ProductCatalog.Entities.Product;
-namespace ProductCatalog.Features.Shared;
+namespace ProductCatalog.Features.Product.Shared;
///
/// Ardalis specification used for the price facet query; applies all filter criteria except the price range so that all price buckets remain visible regardless of the selected price filter.
diff --git a/src/Modules/ProductCatalog/Features/Shared/ProductRequestValidatorBase.cs b/src/Modules/ProductCatalog/Features/Product/Shared/ProductRequestValidatorBase.cs
similarity index 96%
rename from src/Modules/ProductCatalog/Features/Shared/ProductRequestValidatorBase.cs
rename to src/Modules/ProductCatalog/Features/Product/Shared/ProductRequestValidatorBase.cs
index 9247833a..8598f750 100644
--- a/src/Modules/ProductCatalog/Features/Shared/ProductRequestValidatorBase.cs
+++ b/src/Modules/ProductCatalog/Features/Product/Shared/ProductRequestValidatorBase.cs
@@ -1,7 +1,7 @@
using SharedKernel.Application.Validation;
using FluentValidation;
-namespace ProductCatalog.Features.Shared;
+namespace ProductCatalog.Features.Product.Shared;
///
/// Shared FluentValidation extension methods and constants for product-related validation rules.
diff --git a/src/Modules/ProductCatalog/Features/Shared/ProductResponse.cs b/src/Modules/ProductCatalog/Features/Product/Shared/ProductResponse.cs
similarity index 87%
rename from src/Modules/ProductCatalog/Features/Shared/ProductResponse.cs
rename to src/Modules/ProductCatalog/Features/Product/Shared/ProductResponse.cs
index 3e9e9589..d10f8f27 100644
--- a/src/Modules/ProductCatalog/Features/Shared/ProductResponse.cs
+++ b/src/Modules/ProductCatalog/Features/Product/Shared/ProductResponse.cs
@@ -1,4 +1,4 @@
-namespace ProductCatalog.Features.Shared;
+namespace ProductCatalog.Features.Product.Shared;
///
/// Represents a product as returned by the Application layer to API consumers, projected from the domain entity.
diff --git a/src/Modules/ProductCatalog/Features/Shared/ProductSearchFacetsResponse.cs b/src/Modules/ProductCatalog/Features/Product/Shared/ProductSearchFacetsResponse.cs
similarity index 86%
rename from src/Modules/ProductCatalog/Features/Shared/ProductSearchFacetsResponse.cs
rename to src/Modules/ProductCatalog/Features/Product/Shared/ProductSearchFacetsResponse.cs
index da521e1f..c8ff31a4 100644
--- a/src/Modules/ProductCatalog/Features/Shared/ProductSearchFacetsResponse.cs
+++ b/src/Modules/ProductCatalog/Features/Product/Shared/ProductSearchFacetsResponse.cs
@@ -1,4 +1,4 @@
-namespace ProductCatalog.Features.Shared;
+namespace ProductCatalog.Features.Product.Shared;
///
/// Aggregates all facet data returned alongside a product search result, enabling client-side filter refinement.
diff --git a/src/Modules/ProductCatalog/Features/Shared/ProductSortFields.cs b/src/Modules/ProductCatalog/Features/Product/Shared/ProductSortFields.cs
similarity index 93%
rename from src/Modules/ProductCatalog/Features/Shared/ProductSortFields.cs
rename to src/Modules/ProductCatalog/Features/Product/Shared/ProductSortFields.cs
index dec9e07a..66c0987e 100644
--- a/src/Modules/ProductCatalog/Features/Shared/ProductSortFields.cs
+++ b/src/Modules/ProductCatalog/Features/Product/Shared/ProductSortFields.cs
@@ -1,7 +1,7 @@
using SharedKernel.Application.Sorting;
using ProductEntity = ProductCatalog.Entities.Product;
-namespace ProductCatalog.Features.Shared;
+namespace ProductCatalog.Features.Product.Shared;
///
/// Defines the allowed sort fields for product queries and provides the used by specifications to apply ordering.
diff --git a/src/Modules/ProductCatalog/Features/Shared/ProductValidationHelper.cs b/src/Modules/ProductCatalog/Features/Product/Shared/ProductValidationHelper.cs
similarity index 99%
rename from src/Modules/ProductCatalog/Features/Shared/ProductValidationHelper.cs
rename to src/Modules/ProductCatalog/Features/Product/Shared/ProductValidationHelper.cs
index d7d9ee49..6303306d 100644
--- a/src/Modules/ProductCatalog/Features/Shared/ProductValidationHelper.cs
+++ b/src/Modules/ProductCatalog/Features/Product/Shared/ProductValidationHelper.cs
@@ -1,6 +1,6 @@
using SharedKernel.Application.Batch;
-namespace ProductCatalog.Features.Shared;
+namespace ProductCatalog.Features.Product.Shared;
/// Shared validation methods for product commands.
internal static class ProductValidationHelper
diff --git a/src/Modules/ProductCatalog/Features/Shared/ProductsByIdsWithLinksSpecification.cs b/src/Modules/ProductCatalog/Features/Product/Shared/ProductsByIdsWithLinksSpecification.cs
similarity index 92%
rename from src/Modules/ProductCatalog/Features/Shared/ProductsByIdsWithLinksSpecification.cs
rename to src/Modules/ProductCatalog/Features/Product/Shared/ProductsByIdsWithLinksSpecification.cs
index 18c82233..ff572426 100644
--- a/src/Modules/ProductCatalog/Features/Shared/ProductsByIdsWithLinksSpecification.cs
+++ b/src/Modules/ProductCatalog/Features/Product/Shared/ProductsByIdsWithLinksSpecification.cs
@@ -1,7 +1,7 @@
using Ardalis.Specification;
using ProductEntity = ProductCatalog.Entities.Product;
-namespace ProductCatalog.Features.Shared;
+namespace ProductCatalog.Features.Product.Shared;
///
/// Ardalis specification that loads multiple products by their IDs and eagerly includes
diff --git a/src/Modules/ProductCatalog/Features/Shared/ProductsResponse.cs b/src/Modules/ProductCatalog/Features/Product/Shared/ProductsResponse.cs
similarity index 86%
rename from src/Modules/ProductCatalog/Features/Shared/ProductsResponse.cs
rename to src/Modules/ProductCatalog/Features/Product/Shared/ProductsResponse.cs
index 9d457e51..4a166afe 100644
--- a/src/Modules/ProductCatalog/Features/Shared/ProductsResponse.cs
+++ b/src/Modules/ProductCatalog/Features/Product/Shared/ProductsResponse.cs
@@ -1,4 +1,4 @@
-namespace ProductCatalog.Features.Shared;
+namespace ProductCatalog.Features.Product.Shared;
///
/// Combines a paged list of products with their associated search facets in a single response envelope.
diff --git a/src/Modules/ProductCatalog/Features/UpdateProducts/UpdateProductsController.cs b/src/Modules/ProductCatalog/Features/Product/UpdateProducts/ProductsController.UpdateProducts.cs
similarity index 76%
rename from src/Modules/ProductCatalog/Features/UpdateProducts/UpdateProductsController.cs
rename to src/Modules/ProductCatalog/Features/Product/UpdateProducts/ProductsController.UpdateProducts.cs
index aefbe6d5..c5dca43b 100644
--- a/src/Modules/ProductCatalog/Features/UpdateProducts/UpdateProductsController.cs
+++ b/src/Modules/ProductCatalog/Features/Product/UpdateProducts/ProductsController.UpdateProducts.cs
@@ -1,14 +1,12 @@
-using Asp.Versioning;
using ErrorOr;
using Microsoft.AspNetCore.Mvc;
+using ProductCatalog.Features.Product.UpdateProducts;
using SharedKernel.Contracts.Api;
using SharedKernel.Contracts.Security;
-using Wolverine;
-namespace ProductCatalog.Features.UpdateProducts;
+namespace ProductCatalog.Features.Product;
-[ApiVersion(1.0)]
-public sealed class UpdateProductsController(IMessageBus bus) : ApiControllerBase
+public sealed partial class ProductsController
{
/// Updates multiple products in a single batch operation.
[HttpPut]
diff --git a/src/Modules/ProductCatalog/Features/UpdateProducts/UpdateProductItemValidator.cs b/src/Modules/ProductCatalog/Features/Product/UpdateProducts/UpdateProductItemValidator.cs
similarity index 84%
rename from src/Modules/ProductCatalog/Features/UpdateProducts/UpdateProductItemValidator.cs
rename to src/Modules/ProductCatalog/Features/Product/UpdateProducts/UpdateProductItemValidator.cs
index 0b82dd7a..0514d572 100644
--- a/src/Modules/ProductCatalog/Features/UpdateProducts/UpdateProductItemValidator.cs
+++ b/src/Modules/ProductCatalog/Features/Product/UpdateProducts/UpdateProductItemValidator.cs
@@ -1,4 +1,4 @@
-namespace ProductCatalog.Features.UpdateProducts;
+namespace ProductCatalog.Features.Product.UpdateProducts;
///
/// FluentValidation validator for , reusing the shared
diff --git a/src/Modules/ProductCatalog/Features/UpdateProducts/UpdateProductRequest.cs b/src/Modules/ProductCatalog/Features/Product/UpdateProducts/UpdateProductRequest.cs
similarity index 91%
rename from src/Modules/ProductCatalog/Features/UpdateProducts/UpdateProductRequest.cs
rename to src/Modules/ProductCatalog/Features/Product/UpdateProducts/UpdateProductRequest.cs
index b2985fd0..321e4358 100644
--- a/src/Modules/ProductCatalog/Features/UpdateProducts/UpdateProductRequest.cs
+++ b/src/Modules/ProductCatalog/Features/Product/UpdateProducts/UpdateProductRequest.cs
@@ -1,6 +1,6 @@
using System.ComponentModel.DataAnnotations;
-namespace ProductCatalog.Features.UpdateProducts;
+namespace ProductCatalog.Features.Product.UpdateProducts;
///
/// Carries the replacement data for an existing product, subject to the same validation constraints as .
diff --git a/src/Modules/ProductCatalog/Features/UpdateProducts/UpdateProductRequestValidator.cs b/src/Modules/ProductCatalog/Features/Product/UpdateProducts/UpdateProductRequestValidator.cs
similarity index 82%
rename from src/Modules/ProductCatalog/Features/UpdateProducts/UpdateProductRequestValidator.cs
rename to src/Modules/ProductCatalog/Features/Product/UpdateProducts/UpdateProductRequestValidator.cs
index d8c9f619..b8aeb59a 100644
--- a/src/Modules/ProductCatalog/Features/UpdateProducts/UpdateProductRequestValidator.cs
+++ b/src/Modules/ProductCatalog/Features/Product/UpdateProducts/UpdateProductRequestValidator.cs
@@ -1,4 +1,4 @@
-namespace ProductCatalog.Features.UpdateProducts;
+namespace ProductCatalog.Features.Product.UpdateProducts;
///
/// FluentValidation validator for , inheriting all rules from .
diff --git a/src/Modules/ProductCatalog/Features/UpdateProducts/UpdateProductsCommand.cs b/src/Modules/ProductCatalog/Features/Product/UpdateProducts/UpdateProductsCommand.cs
similarity index 98%
rename from src/Modules/ProductCatalog/Features/UpdateProducts/UpdateProductsCommand.cs
rename to src/Modules/ProductCatalog/Features/Product/UpdateProducts/UpdateProductsCommand.cs
index b1b50a79..7c29646e 100644
--- a/src/Modules/ProductCatalog/Features/UpdateProducts/UpdateProductsCommand.cs
+++ b/src/Modules/ProductCatalog/Features/Product/UpdateProducts/UpdateProductsCommand.cs
@@ -9,7 +9,7 @@
using ProductEntity = ProductCatalog.Entities.Product;
using ProductRepositoryContract = ProductCatalog.Interfaces.IProductRepository;
-namespace ProductCatalog.Features.UpdateProducts;
+namespace ProductCatalog.Features.Product.UpdateProducts;
/// Updates multiple products in a single batch operation.
public sealed record UpdateProductsCommand(UpdateProductsRequest Request);
diff --git a/src/Modules/ProductCatalog/Features/UpdateProducts/UpdateProductsRequest.cs b/src/Modules/ProductCatalog/Features/Product/UpdateProducts/UpdateProductsRequest.cs
similarity index 94%
rename from src/Modules/ProductCatalog/Features/UpdateProducts/UpdateProductsRequest.cs
rename to src/Modules/ProductCatalog/Features/Product/UpdateProducts/UpdateProductsRequest.cs
index 6c8a25fe..821ac475 100644
--- a/src/Modules/ProductCatalog/Features/UpdateProducts/UpdateProductsRequest.cs
+++ b/src/Modules/ProductCatalog/Features/Product/UpdateProducts/UpdateProductsRequest.cs
@@ -1,6 +1,6 @@
using System.ComponentModel.DataAnnotations;
-namespace ProductCatalog.Features.UpdateProducts;
+namespace ProductCatalog.Features.Product.UpdateProducts;
///
/// Carries a list of product items to be updated in a single batch operation; accepts between 1 and 100 items.
diff --git a/src/Modules/ProductCatalog/Features/ValidateProductExists/ValidateProductExistsQueryHandler.cs b/src/Modules/ProductCatalog/Features/Product/ValidateProductExists/ValidateProductExistsQueryHandler.cs
similarity index 90%
rename from src/Modules/ProductCatalog/Features/ValidateProductExists/ValidateProductExistsQueryHandler.cs
rename to src/Modules/ProductCatalog/Features/Product/ValidateProductExists/ValidateProductExistsQueryHandler.cs
index 75da8aa1..bc55a27d 100644
--- a/src/Modules/ProductCatalog/Features/ValidateProductExists/ValidateProductExistsQueryHandler.cs
+++ b/src/Modules/ProductCatalog/Features/Product/ValidateProductExists/ValidateProductExistsQueryHandler.cs
@@ -2,7 +2,7 @@
using SharedKernel.Contracts.Queries.ProductCatalog;
using ProductEntity = ProductCatalog.Entities.Product;
-namespace ProductCatalog.Features.ValidateProductExists;
+namespace ProductCatalog.Features.Product.ValidateProductExists;
public sealed class ValidateProductExistsQueryHandler
{
diff --git a/src/Modules/ProductCatalog/Features/CreateImageProductData/CreateImageProductDataCommand.cs b/src/Modules/ProductCatalog/Features/ProductData/CreateImageProductData/CreateImageProductDataCommand.cs
similarity index 95%
rename from src/Modules/ProductCatalog/Features/CreateImageProductData/CreateImageProductDataCommand.cs
rename to src/Modules/ProductCatalog/Features/ProductData/CreateImageProductData/CreateImageProductDataCommand.cs
index 20341c9c..ce977d88 100644
--- a/src/Modules/ProductCatalog/Features/CreateImageProductData/CreateImageProductDataCommand.cs
+++ b/src/Modules/ProductCatalog/Features/ProductData/CreateImageProductData/CreateImageProductDataCommand.cs
@@ -5,7 +5,7 @@
using SharedKernel.Contracts.Events;
using Wolverine;
-namespace ProductCatalog.Features.CreateImageProductData;
+namespace ProductCatalog.Features.ProductData.CreateImageProductData;
public sealed record CreateImageProductDataCommand(CreateImageProductDataRequest Request);
diff --git a/src/Modules/ProductCatalog/Features/CreateImageProductData/CreateImageProductDataRequest.cs b/src/Modules/ProductCatalog/Features/ProductData/CreateImageProductData/CreateImageProductDataRequest.cs
similarity index 94%
rename from src/Modules/ProductCatalog/Features/CreateImageProductData/CreateImageProductDataRequest.cs
rename to src/Modules/ProductCatalog/Features/ProductData/CreateImageProductData/CreateImageProductDataRequest.cs
index 935eb631..0da81366 100644
--- a/src/Modules/ProductCatalog/Features/CreateImageProductData/CreateImageProductDataRequest.cs
+++ b/src/Modules/ProductCatalog/Features/ProductData/CreateImageProductData/CreateImageProductDataRequest.cs
@@ -1,7 +1,7 @@
using System.ComponentModel.DataAnnotations;
using SharedKernel.Application.Validation;
-namespace ProductCatalog.Features.CreateImageProductData;
+namespace ProductCatalog.Features.ProductData.CreateImageProductData;
///
/// Payload for uploading image product data, including dimensions, format, and file size.
diff --git a/src/Modules/ProductCatalog/Features/CreateImageProductData/CreateImageProductDataRequestValidator.cs b/src/Modules/ProductCatalog/Features/ProductData/CreateImageProductData/CreateImageProductDataRequestValidator.cs
similarity index 82%
rename from src/Modules/ProductCatalog/Features/CreateImageProductData/CreateImageProductDataRequestValidator.cs
rename to src/Modules/ProductCatalog/Features/ProductData/CreateImageProductData/CreateImageProductDataRequestValidator.cs
index 76e8139f..0674007c 100644
--- a/src/Modules/ProductCatalog/Features/CreateImageProductData/CreateImageProductDataRequestValidator.cs
+++ b/src/Modules/ProductCatalog/Features/ProductData/CreateImageProductData/CreateImageProductDataRequestValidator.cs
@@ -1,6 +1,6 @@
using SharedKernel.Application.Validation;
-namespace ProductCatalog.Features.CreateImageProductData;
+namespace ProductCatalog.Features.ProductData.CreateImageProductData;
///
/// FluentValidation validator for , delegating to data-annotation-based validation rules.
diff --git a/src/Modules/ProductCatalog/Features/CreateImageProductData/CreateImageProductDataController.cs b/src/Modules/ProductCatalog/Features/ProductData/CreateImageProductData/ProductDataController.CreateImage.cs
similarity index 74%
rename from src/Modules/ProductCatalog/Features/CreateImageProductData/CreateImageProductDataController.cs
rename to src/Modules/ProductCatalog/Features/ProductData/CreateImageProductData/ProductDataController.CreateImage.cs
index 35e68933..640ee08f 100644
--- a/src/Modules/ProductCatalog/Features/CreateImageProductData/CreateImageProductDataController.cs
+++ b/src/Modules/ProductCatalog/Features/ProductData/CreateImageProductData/ProductDataController.CreateImage.cs
@@ -1,15 +1,12 @@
-using Asp.Versioning;
using ErrorOr;
using Microsoft.AspNetCore.Mvc;
+using ProductCatalog.Features.ProductData.CreateImageProductData;
using SharedKernel.Contracts.Api;
using SharedKernel.Contracts.Security;
-using Wolverine;
-namespace ProductCatalog.Features.CreateImageProductData;
+namespace ProductCatalog.Features.ProductData;
-[ApiVersion(1.0)]
-[Route("api/v{version:apiVersion}/product-data")]
-public sealed class CreateImageProductDataController(IMessageBus bus) : ApiControllerBase
+public sealed partial class ProductDataController
{
/// Creates a new image product-data document and returns it with a 201 Location header.
[HttpPost("image")]
diff --git a/src/Modules/ProductCatalog/Features/CreateVideoProductData/CreateVideoProductDataCommand.cs b/src/Modules/ProductCatalog/Features/ProductData/CreateVideoProductData/CreateVideoProductDataCommand.cs
similarity index 95%
rename from src/Modules/ProductCatalog/Features/CreateVideoProductData/CreateVideoProductDataCommand.cs
rename to src/Modules/ProductCatalog/Features/ProductData/CreateVideoProductData/CreateVideoProductDataCommand.cs
index 45938112..d1728041 100644
--- a/src/Modules/ProductCatalog/Features/CreateVideoProductData/CreateVideoProductDataCommand.cs
+++ b/src/Modules/ProductCatalog/Features/ProductData/CreateVideoProductData/CreateVideoProductDataCommand.cs
@@ -5,7 +5,7 @@
using SharedKernel.Contracts.Events;
using Wolverine;
-namespace ProductCatalog.Features.CreateVideoProductData;
+namespace ProductCatalog.Features.ProductData.CreateVideoProductData;
public sealed record CreateVideoProductDataCommand(CreateVideoProductDataRequest Request);
diff --git a/src/Modules/ProductCatalog/Features/CreateVideoProductData/CreateVideoProductDataRequest.cs b/src/Modules/ProductCatalog/Features/ProductData/CreateVideoProductData/CreateVideoProductDataRequest.cs
similarity index 94%
rename from src/Modules/ProductCatalog/Features/CreateVideoProductData/CreateVideoProductDataRequest.cs
rename to src/Modules/ProductCatalog/Features/ProductData/CreateVideoProductData/CreateVideoProductDataRequest.cs
index c5b94654..a1c40a98 100644
--- a/src/Modules/ProductCatalog/Features/CreateVideoProductData/CreateVideoProductDataRequest.cs
+++ b/src/Modules/ProductCatalog/Features/ProductData/CreateVideoProductData/CreateVideoProductDataRequest.cs
@@ -1,7 +1,7 @@
using System.ComponentModel.DataAnnotations;
using SharedKernel.Application.Validation;
-namespace ProductCatalog.Features.CreateVideoProductData;
+namespace ProductCatalog.Features.ProductData.CreateVideoProductData;
///
/// Payload for uploading video product data, including duration, resolution, format, and file size.
diff --git a/src/Modules/ProductCatalog/Features/CreateVideoProductData/CreateVideoProductDataRequestValidator.cs b/src/Modules/ProductCatalog/Features/ProductData/CreateVideoProductData/CreateVideoProductDataRequestValidator.cs
similarity index 82%
rename from src/Modules/ProductCatalog/Features/CreateVideoProductData/CreateVideoProductDataRequestValidator.cs
rename to src/Modules/ProductCatalog/Features/ProductData/CreateVideoProductData/CreateVideoProductDataRequestValidator.cs
index bd8140f2..4e7fa9b6 100644
--- a/src/Modules/ProductCatalog/Features/CreateVideoProductData/CreateVideoProductDataRequestValidator.cs
+++ b/src/Modules/ProductCatalog/Features/ProductData/CreateVideoProductData/CreateVideoProductDataRequestValidator.cs
@@ -1,6 +1,6 @@
using SharedKernel.Application.Validation;
-namespace ProductCatalog.Features.CreateVideoProductData;
+namespace ProductCatalog.Features.ProductData.CreateVideoProductData;
///
/// FluentValidation validator for , delegating to data-annotation-based validation rules.
diff --git a/src/Modules/ProductCatalog/Features/CreateVideoProductData/CreateVideoProductDataController.cs b/src/Modules/ProductCatalog/Features/ProductData/CreateVideoProductData/ProductDataController.CreateVideo.cs
similarity index 74%
rename from src/Modules/ProductCatalog/Features/CreateVideoProductData/CreateVideoProductDataController.cs
rename to src/Modules/ProductCatalog/Features/ProductData/CreateVideoProductData/ProductDataController.CreateVideo.cs
index aaaea6d6..9e5230dd 100644
--- a/src/Modules/ProductCatalog/Features/CreateVideoProductData/CreateVideoProductDataController.cs
+++ b/src/Modules/ProductCatalog/Features/ProductData/CreateVideoProductData/ProductDataController.CreateVideo.cs
@@ -1,15 +1,12 @@
-using Asp.Versioning;
using ErrorOr;
using Microsoft.AspNetCore.Mvc;
+using ProductCatalog.Features.ProductData.CreateVideoProductData;
using SharedKernel.Contracts.Api;
using SharedKernel.Contracts.Security;
-using Wolverine;
-namespace ProductCatalog.Features.CreateVideoProductData;
+namespace ProductCatalog.Features.ProductData;
-[ApiVersion(1.0)]
-[Route("api/v{version:apiVersion}/product-data")]
-public sealed class CreateVideoProductDataController(IMessageBus bus) : ApiControllerBase
+public sealed partial class ProductDataController
{
/// Creates a new video product-data document and returns it with a 201 Location header.
[HttpPost("video")]
diff --git a/src/Modules/ProductCatalog/Features/DeleteProductData/DeleteProductDataCommand.cs b/src/Modules/ProductCatalog/Features/ProductData/DeleteProductData/DeleteProductDataCommand.cs
similarity index 98%
rename from src/Modules/ProductCatalog/Features/DeleteProductData/DeleteProductDataCommand.cs
rename to src/Modules/ProductCatalog/Features/ProductData/DeleteProductData/DeleteProductDataCommand.cs
index 109be42a..2e52f5b8 100644
--- a/src/Modules/ProductCatalog/Features/DeleteProductData/DeleteProductDataCommand.cs
+++ b/src/Modules/ProductCatalog/Features/ProductData/DeleteProductData/DeleteProductDataCommand.cs
@@ -6,7 +6,7 @@
using ProductCatalog;
using Wolverine;
-namespace ProductCatalog.Features.DeleteProductData;
+namespace ProductCatalog.Features.ProductData.DeleteProductData;
public sealed record DeleteProductDataCommand(Guid Id) : IHasId;
diff --git a/src/Modules/ProductCatalog/Features/DeleteProductData/ProductDataCascadeDeleteHandler.cs b/src/Modules/ProductCatalog/Features/ProductData/DeleteProductData/ProductDataCascadeDeleteHandler.cs
similarity index 94%
rename from src/Modules/ProductCatalog/Features/DeleteProductData/ProductDataCascadeDeleteHandler.cs
rename to src/Modules/ProductCatalog/Features/ProductData/DeleteProductData/ProductDataCascadeDeleteHandler.cs
index 1d3f2896..27d3831c 100644
--- a/src/Modules/ProductCatalog/Features/DeleteProductData/ProductDataCascadeDeleteHandler.cs
+++ b/src/Modules/ProductCatalog/Features/ProductData/DeleteProductData/ProductDataCascadeDeleteHandler.cs
@@ -3,7 +3,7 @@
using Polly.Registry;
using ProductCatalog.Logging;
-namespace ProductCatalog.Features.DeleteProductData;
+namespace ProductCatalog.Features.ProductData.DeleteProductData;
public sealed class ProductDataCascadeDeleteHandler
{
diff --git a/src/Modules/ProductCatalog/Features/DeleteProductData/DeleteProductDataController.cs b/src/Modules/ProductCatalog/Features/ProductData/DeleteProductData/ProductDataController.Delete.cs
similarity index 69%
rename from src/Modules/ProductCatalog/Features/DeleteProductData/DeleteProductDataController.cs
rename to src/Modules/ProductCatalog/Features/ProductData/DeleteProductData/ProductDataController.Delete.cs
index 409bebcc..28c0cd2d 100644
--- a/src/Modules/ProductCatalog/Features/DeleteProductData/DeleteProductDataController.cs
+++ b/src/Modules/ProductCatalog/Features/ProductData/DeleteProductData/ProductDataController.Delete.cs
@@ -1,15 +1,12 @@
-using Asp.Versioning;
using ErrorOr;
using Microsoft.AspNetCore.Mvc;
+using ProductCatalog.Features.ProductData.DeleteProductData;
using SharedKernel.Contracts.Api;
using SharedKernel.Contracts.Security;
-using Wolverine;
-namespace ProductCatalog.Features.DeleteProductData;
+namespace ProductCatalog.Features.ProductData;
-[ApiVersion(1.0)]
-[Route("api/v{version:apiVersion}/product-data")]
-public sealed class DeleteProductDataController(IMessageBus bus) : ApiControllerBase
+public sealed partial class ProductDataController
{
/// Deletes a product data document by its identifier.
[HttpDelete("{id:guid}")]
diff --git a/src/Modules/ProductCatalog/Features/GetProductData/GetProductDataQuery.cs b/src/Modules/ProductCatalog/Features/ProductData/GetProductData/GetProductDataQuery.cs
similarity index 89%
rename from src/Modules/ProductCatalog/Features/GetProductData/GetProductDataQuery.cs
rename to src/Modules/ProductCatalog/Features/ProductData/GetProductData/GetProductDataQuery.cs
index 515f3046..1fe559e2 100644
--- a/src/Modules/ProductCatalog/Features/GetProductData/GetProductDataQuery.cs
+++ b/src/Modules/ProductCatalog/Features/ProductData/GetProductData/GetProductDataQuery.cs
@@ -1,6 +1,6 @@
using ErrorOr;
-namespace ProductCatalog.Features.GetProductData;
+namespace ProductCatalog.Features.ProductData.GetProductData;
public sealed record GetProductDataQuery(string? Type);
diff --git a/src/Modules/ProductCatalog/Features/GetProductData/GetProductDataController.cs b/src/Modules/ProductCatalog/Features/ProductData/GetProductData/ProductDataController.GetAll.cs
similarity index 75%
rename from src/Modules/ProductCatalog/Features/GetProductData/GetProductDataController.cs
rename to src/Modules/ProductCatalog/Features/ProductData/GetProductData/ProductDataController.GetAll.cs
index c0a199b6..9657543d 100644
--- a/src/Modules/ProductCatalog/Features/GetProductData/GetProductDataController.cs
+++ b/src/Modules/ProductCatalog/Features/ProductData/GetProductData/ProductDataController.GetAll.cs
@@ -1,16 +1,13 @@
-using Asp.Versioning;
using ErrorOr;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.OutputCaching;
+using ProductCatalog.Features.ProductData.GetProductData;
using SharedKernel.Contracts.Api;
using SharedKernel.Contracts.Security;
-using Wolverine;
-namespace ProductCatalog.Features.GetProductData;
+namespace ProductCatalog.Features.ProductData;
-[ApiVersion(1.0)]
-[Route("api/v{version:apiVersion}/product-data")]
-public sealed class GetProductDataController(IMessageBus bus) : ApiControllerBase
+public sealed partial class ProductDataController
{
/// Returns all product data documents, optionally filtered by type.
[HttpGet]
diff --git a/src/Modules/ProductCatalog/Features/GetProductDataById/GetProductDataByIdQuery.cs b/src/Modules/ProductCatalog/Features/ProductData/GetProductDataById/GetProductDataByIdQuery.cs
similarity index 91%
rename from src/Modules/ProductCatalog/Features/GetProductDataById/GetProductDataByIdQuery.cs
rename to src/Modules/ProductCatalog/Features/ProductData/GetProductDataById/GetProductDataByIdQuery.cs
index 0c94174a..3bcccd13 100644
--- a/src/Modules/ProductCatalog/Features/GetProductDataById/GetProductDataByIdQuery.cs
+++ b/src/Modules/ProductCatalog/Features/ProductData/GetProductDataById/GetProductDataByIdQuery.cs
@@ -1,7 +1,7 @@
using ErrorOr;
using SharedKernel.Application.Context;
-namespace ProductCatalog.Features.GetProductDataById;
+namespace ProductCatalog.Features.ProductData.GetProductDataById;
public sealed record GetProductDataByIdQuery(Guid Id) : IHasId;
diff --git a/src/Modules/ProductCatalog/Features/GetProductDataById/GetProductDataByIdController.cs b/src/Modules/ProductCatalog/Features/ProductData/GetProductDataById/ProductDataController.GetById.cs
similarity index 74%
rename from src/Modules/ProductCatalog/Features/GetProductDataById/GetProductDataByIdController.cs
rename to src/Modules/ProductCatalog/Features/ProductData/GetProductDataById/ProductDataController.GetById.cs
index fef62c90..ddf38539 100644
--- a/src/Modules/ProductCatalog/Features/GetProductDataById/GetProductDataByIdController.cs
+++ b/src/Modules/ProductCatalog/Features/ProductData/GetProductDataById/ProductDataController.GetById.cs
@@ -1,16 +1,13 @@
-using Asp.Versioning;
using ErrorOr;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.OutputCaching;
+using ProductCatalog.Features.ProductData.GetProductDataById;
using SharedKernel.Contracts.Api;
using SharedKernel.Contracts.Security;
-using Wolverine;
-namespace ProductCatalog.Features.GetProductDataById;
+namespace ProductCatalog.Features.ProductData;
-[ApiVersion(1.0)]
-[Route("api/v{version:apiVersion}/product-data")]
-public sealed class GetProductDataByIdController(IMessageBus bus) : ApiControllerBase
+public sealed partial class ProductDataController
{
/// Returns a single product data document by its identifier, or 404 if not found.
[HttpGet("{id:guid}")]
diff --git a/src/Modules/ProductCatalog/Features/ProductData/ProductDataController.cs b/src/Modules/ProductCatalog/Features/ProductData/ProductDataController.cs
new file mode 100644
index 00000000..7ec53b43
--- /dev/null
+++ b/src/Modules/ProductCatalog/Features/ProductData/ProductDataController.cs
@@ -0,0 +1,8 @@
+using Asp.Versioning;
+using SharedKernel.Contracts.Api;
+using Wolverine;
+
+namespace ProductCatalog.Features.ProductData;
+
+[ApiVersion(1.0)]
+public sealed partial class ProductDataController(IMessageBus bus) : ApiControllerBase { }
diff --git a/src/Modules/ProductCatalog/Features/Shared/ProductDataMappings.cs b/src/Modules/ProductCatalog/Features/ProductData/Shared/ProductDataMappings.cs
similarity index 97%
rename from src/Modules/ProductCatalog/Features/Shared/ProductDataMappings.cs
rename to src/Modules/ProductCatalog/Features/ProductData/Shared/ProductDataMappings.cs
index ceef7536..986b5c91 100644
--- a/src/Modules/ProductCatalog/Features/Shared/ProductDataMappings.cs
+++ b/src/Modules/ProductCatalog/Features/ProductData/Shared/ProductDataMappings.cs
@@ -2,7 +2,7 @@
using ProductDataEntity = ProductCatalog.Entities.ProductData.ProductData;
using VideoProductDataEntity = ProductCatalog.Entities.ProductData.VideoProductData;
-namespace ProductCatalog.Features.Shared;
+namespace ProductCatalog.Features.ProductData.Shared;
///
/// Provides mapping utilities from product data domain entities to their polymorphic response DTOs.
diff --git a/src/Modules/ProductCatalog/Features/Shared/ProductDataResponse.cs b/src/Modules/ProductCatalog/Features/ProductData/Shared/ProductDataResponse.cs
similarity index 96%
rename from src/Modules/ProductCatalog/Features/Shared/ProductDataResponse.cs
rename to src/Modules/ProductCatalog/Features/ProductData/Shared/ProductDataResponse.cs
index 9a813234..7110c2d7 100644
--- a/src/Modules/ProductCatalog/Features/Shared/ProductDataResponse.cs
+++ b/src/Modules/ProductCatalog/Features/ProductData/Shared/ProductDataResponse.cs
@@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
-namespace ProductCatalog.Features.Shared;
+namespace ProductCatalog.Features.ProductData.Shared;
///
/// Abstract base read model for product data, serialised as a polymorphic type using the type discriminator.
diff --git a/src/Modules/ProductCatalog/Features/Shared/Routing/ProductCatalogRouteTemplates.cs b/src/Modules/ProductCatalog/Features/Shared/Routing/ProductCatalogRouteTemplates.cs
new file mode 100644
index 00000000..3f5f2908
--- /dev/null
+++ b/src/Modules/ProductCatalog/Features/Shared/Routing/ProductCatalogRouteTemplates.cs
@@ -0,0 +1,8 @@
+namespace ProductCatalog.Features.Shared.Routing;
+
+/// Shared path fragments for ProductCatalog where not implied by [controller].
+public static class ProductCatalogRouteTemplates
+{
+ /// Path segment for 201 Location on idempotent POST (matches public URL casing).
+ public const string IdempotentPathSegment = "idempotent";
+}
diff --git a/src/Modules/ProductCatalog/GlobalUsings.cs b/src/Modules/ProductCatalog/GlobalUsings.cs
index 6191a45c..68e9600a 100644
--- a/src/Modules/ProductCatalog/GlobalUsings.cs
+++ b/src/Modules/ProductCatalog/GlobalUsings.cs
@@ -1,16 +1,18 @@
// ── Domain ────────────────────────────────────────────────────────────────────
-global using SharedKernel.Domain.Common;
-global using SharedKernel.Domain.Entities;
-global using SharedKernel.Domain.Entities.Contracts;
-global using SharedKernel.Domain.Interfaces;
-
// ── Application ───────────────────────────────────────────────────────────────
global using ProductCatalog.Common.Errors;
global using ProductCatalog.Common.Events;
-global using ProductCatalog.Features.Shared;
global using ProductCatalog.Entities;
global using ProductCatalog.Entities.ProductData;
+global using ProductCatalog.Features.Category.Shared;
+global using ProductCatalog.Features.Product.Shared;
+global using ProductCatalog.Features.ProductData.Shared;
+global using ProductCatalog.Features.Shared.Routing;
+// ── API / Presentation ────────────────────────────────────────────────────────
+global using ProductCatalog.GraphQL.Models;
global using ProductCatalog.Interfaces;
+global using Reviews.Domain;
+global using Reviews.Features;
global using SharedKernel.Application.Batch;
global using SharedKernel.Application.Batch.Rules;
global using SharedKernel.Application.Context;
@@ -18,25 +20,23 @@
global using SharedKernel.Application.DTOs;
global using SharedKernel.Application.Events;
global using SharedKernel.Application.Extensions;
+// ── Infrastructure ────────────────────────────────────────────────────────────
+global using SharedKernel.Application.Options.Infrastructure;
global using SharedKernel.Application.Resilience;
global using SharedKernel.Application.Search;
global using SharedKernel.Application.Sorting;
global using SharedKernel.Application.Validation;
-global using SharedKernel.Contracts.Events;
-
-// ── API / Presentation ────────────────────────────────────────────────────────
-global using ProductCatalog.GraphQL.Models;
-global using Reviews.Domain;
-global using Reviews.Features;
global using SharedKernel.Contracts.Api;
+global using SharedKernel.Contracts.Events;
global using SharedKernel.Contracts.Security;
-global using Error = ErrorOr.Error;
-
-// ── Infrastructure ────────────────────────────────────────────────────────────
-global using SharedKernel.Application.Options.Infrastructure;
+global using SharedKernel.Domain.Common;
+global using SharedKernel.Domain.Entities;
+global using SharedKernel.Domain.Entities.Contracts;
+global using SharedKernel.Domain.Interfaces;
global using SharedKernel.Infrastructure.Configurations;
global using SharedKernel.Infrastructure.Persistence;
global using SharedKernel.Infrastructure.Repositories;
global using SharedKernel.Infrastructure.SoftDelete;
global using SharedKernel.Infrastructure.StoredProcedures;
global using SharedKernel.Infrastructure.UnitOfWork;
+global using Error = ErrorOr.Error;
diff --git a/src/Modules/ProductCatalog/GraphQL/Mutations/ProductMutations.cs b/src/Modules/ProductCatalog/GraphQL/Mutations/ProductMutations.cs
index 1d13f0d3..b2f33b01 100644
--- a/src/Modules/ProductCatalog/GraphQL/Mutations/ProductMutations.cs
+++ b/src/Modules/ProductCatalog/GraphQL/Mutations/ProductMutations.cs
@@ -1,7 +1,7 @@
using ErrorOr;
using HotChocolate.Authorization;
-using ProductCatalog.Features.CreateProducts;
-using ProductCatalog.Features.DeleteProducts;
+using ProductCatalog.Features.Product.CreateProducts;
+using ProductCatalog.Features.Product.DeleteProducts;
using SharedKernel.Contracts.Security;
using Wolverine;
@@ -59,4 +59,3 @@ CancellationToken ct
return result.ToGraphQLResult();
}
}
-
diff --git a/src/Modules/ProductCatalog/GraphQL/Queries/CategoryQueries.cs b/src/Modules/ProductCatalog/GraphQL/Queries/CategoryQueries.cs
index 979dfe19..ce32c8a7 100644
--- a/src/Modules/ProductCatalog/GraphQL/Queries/CategoryQueries.cs
+++ b/src/Modules/ProductCatalog/GraphQL/Queries/CategoryQueries.cs
@@ -1,7 +1,7 @@
using ErrorOr;
using HotChocolate.Authorization;
-using ProductCatalog.Features.GetCategories;
-using ProductCatalog.Features.GetCategoryById;
+using ProductCatalog.Features.Category.GetCategories;
+using ProductCatalog.Features.Category.GetCategoryById;
using ProductCatalog.GraphQL.Models;
using Wolverine;
@@ -53,4 +53,3 @@ CancellationToken ct
return result.ToGraphQLNullableResult();
}
}
-
diff --git a/src/Modules/ProductCatalog/GraphQL/Queries/ProductQueries.cs b/src/Modules/ProductCatalog/GraphQL/Queries/ProductQueries.cs
index 968b6195..de6a3dad 100644
--- a/src/Modules/ProductCatalog/GraphQL/Queries/ProductQueries.cs
+++ b/src/Modules/ProductCatalog/GraphQL/Queries/ProductQueries.cs
@@ -1,7 +1,7 @@
using ErrorOr;
using HotChocolate.Authorization;
-using ProductCatalog.Features.GetProductById;
-using ProductCatalog.Features.GetProducts;
+using ProductCatalog.Features.Product.GetProductById;
+using ProductCatalog.Features.Product.GetProducts;
using ProductCatalog.GraphQL.Models;
using Wolverine;
@@ -61,4 +61,3 @@ CancellationToken ct
return result.ToGraphQLNullableResult();
}
}
-
diff --git a/src/Modules/ProductCatalog/Interfaces/IProductRepository.cs b/src/Modules/ProductCatalog/Interfaces/IProductRepository.cs
index 8105ad48..d6d70748 100644
--- a/src/Modules/ProductCatalog/Interfaces/IProductRepository.cs
+++ b/src/Modules/ProductCatalog/Interfaces/IProductRepository.cs
@@ -1,5 +1,5 @@
using ErrorOr;
-using ProductCatalog.Features.GetProducts;
+using ProductCatalog.Features.Product.GetProducts;
using ProductEntity = ProductCatalog.Entities.Product;
namespace ProductCatalog.Interfaces;
diff --git a/src/Modules/ProductCatalog/Repositories/ProductRepository.cs b/src/Modules/ProductCatalog/Repositories/ProductRepository.cs
index 5c059e12..e1baa1cc 100644
--- a/src/Modules/ProductCatalog/Repositories/ProductRepository.cs
+++ b/src/Modules/ProductCatalog/Repositories/ProductRepository.cs
@@ -1,6 +1,6 @@
using ErrorOr;
using Microsoft.EntityFrameworkCore;
-using ProductCatalog.Features.GetProducts;
+using ProductCatalog.Features.Product.GetProducts;
using ProductCatalog.Persistence;
using ProductApplicationRepository = ProductCatalog.Interfaces.IProductRepository;
@@ -144,4 +144,3 @@ public int[] ToArray() =>
];
}
}
-
diff --git a/tests/APITemplate.Tests/Integration/Auth/UnauthorizedAccessTests.cs b/tests/APITemplate.Tests/Integration/Auth/UnauthorizedAccessTests.cs
index 59da8d60..c829d7ff 100644
--- a/tests/APITemplate.Tests/Integration/Auth/UnauthorizedAccessTests.cs
+++ b/tests/APITemplate.Tests/Integration/Auth/UnauthorizedAccessTests.cs
@@ -18,7 +18,7 @@ public UnauthorizedAccessTests(CustomWebApplicationFactory factory)
[InlineData("/api/v1/products")]
[InlineData("/api/v1/categories")]
[InlineData("/api/v1/productreviews")]
- [InlineData("/api/v1/product-data")]
+ [InlineData("/api/v1/ProductData")]
[InlineData("/api/v1/categories/00000000-0000-0000-0000-000000000001/stats")]
public async Task GetEndpoint_WithoutToken_ReturnsUnauthorized(string endpoint)
{
diff --git a/tests/APITemplate.Tests/Integration/Features/IdempotentControllerTests.cs b/tests/APITemplate.Tests/Integration/Features/IdempotentControllerTests.cs
index c655e1d2..041eb953 100644
--- a/tests/APITemplate.Tests/Integration/Features/IdempotentControllerTests.cs
+++ b/tests/APITemplate.Tests/Integration/Features/IdempotentControllerTests.cs
@@ -2,7 +2,7 @@
using System.Net.Http.Json;
using System.Text.Json;
using APITemplate.Tests.Integration.Helpers;
-using ProductCatalog.Features.IdempotentCreate;
+using ProductCatalog.Features.Product.IdempotentCreate;
using Shouldly;
using Xunit;
diff --git a/tests/APITemplate.Tests/Integration/Postgres/PostgresSoftDeleteTests.cs b/tests/APITemplate.Tests/Integration/Postgres/PostgresSoftDeleteTests.cs
index 288cfcf0..f89a865b 100644
--- a/tests/APITemplate.Tests/Integration/Postgres/PostgresSoftDeleteTests.cs
+++ b/tests/APITemplate.Tests/Integration/Postgres/PostgresSoftDeleteTests.cs
@@ -388,7 +388,7 @@ public async Task DeleteProductData_SoftDeletesLinksAndMongoDocument()
}
IntegrationAuthHelper.Authenticate(_client, tenantId: tenantId);
- var response = await _client.DeleteAsync($"/api/v1/product-data/{productDataId}", ct);
+ var response = await _client.DeleteAsync($"/api/v1/ProductData/{productDataId}", ct);
response.StatusCode.ShouldBe(HttpStatusCode.NoContent);
await using var verifyContext = await CreateDbContextAsync(false, Guid.Empty, actorId, ct);
diff --git a/tests/APITemplate.Tests/Integration/Products/ProductDataControllerTests.cs b/tests/APITemplate.Tests/Integration/Products/ProductDataControllerTests.cs
index a22eb27d..f5954609 100644
--- a/tests/APITemplate.Tests/Integration/Products/ProductDataControllerTests.cs
+++ b/tests/APITemplate.Tests/Integration/Products/ProductDataControllerTests.cs
@@ -32,7 +32,7 @@ public async Task GetAll_WithToken_ReturnsOk()
.Setup(r => r.GetAllAsync(null, It.IsAny()))
.ReturnsAsync([]);
- var response = await _client.GetAsync("/api/v1/product-data", ct);
+ var response = await _client.GetAsync("/api/v1/ProductData", ct);
response.StatusCode.ShouldBe(HttpStatusCode.OK);
}
@@ -56,7 +56,7 @@ public async Task GetAll_WithTypeFilter_PassesTypeToRepository()
},
]);
- var response = await _client.GetAsync("/api/v1/product-data?type=image", ct);
+ var response = await _client.GetAsync("/api/v1/ProductData?type=image", ct);
response.StatusCode.ShouldBe(HttpStatusCode.OK);
var items = await response.Content.ReadFromJsonAsync(
@@ -88,7 +88,7 @@ public async Task GetById_WhenExists_ReturnsOk()
.Setup(r => r.GetByIdAsync(image.Id, It.IsAny()))
.ReturnsAsync(image);
- var response = await _client.GetAsync($"/api/v1/product-data/{image.Id}", ct);
+ var response = await _client.GetAsync($"/api/v1/ProductData/{image.Id}", ct);
response.StatusCode.ShouldBe(HttpStatusCode.OK);
var data = await response.Content.ReadFromJsonAsync(
@@ -110,7 +110,7 @@ public async Task GetById_WhenNotFound_ReturnsNotFound()
.Setup(r => r.GetByIdAsync(It.IsAny(), It.IsAny()))
.ReturnsAsync((ProductData?)null);
- var response = await _client.GetAsync($"/api/v1/product-data/{Guid.NewGuid()}", ct);
+ var response = await _client.GetAsync($"/api/v1/ProductData/{Guid.NewGuid()}", ct);
response.StatusCode.ShouldBe(HttpStatusCode.NotFound);
}
@@ -160,7 +160,7 @@ public async Task Create_ValidRequest_ReturnsCreated(string type)
FileSizeBytes = 10000000,
};
- var response = await _client.PostAsJsonAsync($"/api/v1/product-data/{type}", payload, ct);
+ var response = await _client.PostAsJsonAsync($"/api/v1/ProductData/{type}", payload, ct);
var body = await response.Content.ReadAsStringAsync(ct);
response.StatusCode.ShouldBe(HttpStatusCode.Created, body);
@@ -199,7 +199,7 @@ public async Task Create_InvalidRequest_ReturnsBadRequest(string type)
FileSizeBytes = -1,
};
- var response = await _client.PostAsJsonAsync($"/api/v1/product-data/{type}", payload, ct);
+ var response = await _client.PostAsJsonAsync($"/api/v1/ProductData/{type}", payload, ct);
response.StatusCode.ShouldBe(HttpStatusCode.BadRequest);
}
@@ -223,7 +223,7 @@ public async Task Delete_WithToken_ReturnsNoContent()
}
);
- var response = await _client.DeleteAsync($"/api/v1/product-data/{id}", ct);
+ var response = await _client.DeleteAsync($"/api/v1/ProductData/{id}", ct);
response.StatusCode.ShouldBe(HttpStatusCode.NoContent);
_repositoryMock.Verify(
diff --git a/tests/APITemplate.Tests/Unit/ProductCatalog/PatchProductCommandHandlerTests.cs b/tests/APITemplate.Tests/Unit/ProductCatalog/PatchProductCommandHandlerTests.cs
index 451660f8..3748459d 100644
--- a/tests/APITemplate.Tests/Unit/ProductCatalog/PatchProductCommandHandlerTests.cs
+++ b/tests/APITemplate.Tests/Unit/ProductCatalog/PatchProductCommandHandlerTests.cs
@@ -4,10 +4,10 @@
using Identity.ValueObjects;
using Moq;
using ProductCatalog;
-using ProductCatalog.Entities;
using ProductCatalog.Common.Events;
-using ProductCatalog.Features.PatchProduct;
-using ProductCatalog.Features.Shared;
+using ProductCatalog.Entities;
+using ProductCatalog.Features.Product.PatchProduct;
+using ProductCatalog.Features.Product.Shared;
using ProductCatalog.Interfaces;
using ProductCatalog.ValueObjects;
using SharedKernel.Contracts.Events;
diff --git a/tests/APITemplate.Tests/Unit/ProductCatalog/ProductCatalogModuleTests.cs b/tests/APITemplate.Tests/Unit/ProductCatalog/ProductCatalogModuleTests.cs
index 00fa8905..93d6e171 100644
--- a/tests/APITemplate.Tests/Unit/ProductCatalog/ProductCatalogModuleTests.cs
+++ b/tests/APITemplate.Tests/Unit/ProductCatalog/ProductCatalogModuleTests.cs
@@ -1,10 +1,10 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using ProductCatalog;
-using ProductCatalog.Features.CreateCategories;
-using ProductCatalog.Features.CreateProducts;
-using ProductCatalog.Features.UpdateCategories;
-using ProductCatalog.Features.UpdateProducts;
+using ProductCatalog.Features.Category.CreateCategories;
+using ProductCatalog.Features.Category.UpdateCategories;
+using ProductCatalog.Features.Product.CreateProducts;
+using ProductCatalog.Features.Product.UpdateProducts;
using SharedKernel.Application.Batch;
using SharedKernel.Application.Batch.Rules;
using Shouldly;
diff --git a/tests/APITemplate.Tests/Unit/Validators/CreateProductRequestValidatorTests.cs b/tests/APITemplate.Tests/Unit/Validators/CreateProductRequestValidatorTests.cs
index b0e7af89..0f15514d 100644
--- a/tests/APITemplate.Tests/Unit/Validators/CreateProductRequestValidatorTests.cs
+++ b/tests/APITemplate.Tests/Unit/Validators/CreateProductRequestValidatorTests.cs
@@ -1,4 +1,4 @@
-using ProductCatalog.Features.CreateProducts;
+using ProductCatalog.Features.Product.CreateProducts;
using Shouldly;
using Xunit;
diff --git a/tests/APITemplate.Tests/Unit/Validators/UpdateProductRequestValidatorTests.cs b/tests/APITemplate.Tests/Unit/Validators/UpdateProductRequestValidatorTests.cs
index 174b3543..92f6960f 100644
--- a/tests/APITemplate.Tests/Unit/Validators/UpdateProductRequestValidatorTests.cs
+++ b/tests/APITemplate.Tests/Unit/Validators/UpdateProductRequestValidatorTests.cs
@@ -1,4 +1,4 @@
-using ProductCatalog.Features.UpdateProducts;
+using ProductCatalog.Features.Product.UpdateProducts;
using Shouldly;
using Xunit;