This commit is contained in:
2025-10-27 21:39:50 -04:00
parent 31f723bea4
commit 2fecd5b315
22 changed files with 1198 additions and 14 deletions

View File

@@ -4,11 +4,13 @@ using Prefab.Domain.Exceptions;
using Prefab.Endpoints;
using Prefab.Shared.Catalog;
using Prefab.Shared.Catalog.Products;
using Prefab.Shared;
using Prefab.Web.Client.Models.Catalog;
using Prefab.Web.Client.Models.Shared;
using Prefab.Web.Client.ViewModels.Catalog;
using Prefab.Web.Client.Services;
using UrlBuilder = Prefab.Web.UrlPolicy.UrlPolicy;
using CatalogNotFoundException = Prefab.Catalog.Domain.Exceptions.CatalogNotFoundException;
namespace Prefab.Web.Gateway;
@@ -29,6 +31,18 @@ public static class Products
return Results.Json(result, statusCode: status);
})
.WithTags(nameof(Products));
endpoints.MapGet(
"/bff/catalog/products/{slug}",
async (DetailService service, string slug, CancellationToken cancellationToken) =>
{
var result = await service.Get(slug, cancellationToken);
var status = result.Problem?.StatusCode ?? (result.IsSuccess
? StatusCodes.Status200OK
: StatusCodes.Status500InternalServerError);
return Results.Json(result, statusCode: status);
})
.WithTags(nameof(Products));
}
}
@@ -117,4 +131,77 @@ public static class Products
};
}
}
public sealed class DetailService(IModuleClient moduleClient)
{
public async Task<Result<ProductDisplayModel>> Get(string slug, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(slug))
{
return Result<ProductDisplayModel>.Failure(new ResultProblem
{
Title = "Invalid product slug.",
Detail = "Product slug is required.",
StatusCode = StatusCodes.Status400BadRequest
});
}
var normalizedSlug = slug.Trim();
try
{
var response = await moduleClient.Product.GetProductConfig(normalizedSlug, cancellationToken);
var product = response.Product ?? throw new InvalidOperationException("Product payload was empty.");
var model = ProductDisplayModel.From(product);
return Result<ProductDisplayModel>.Success(model);
}
catch (ValidationException validationException)
{
var problem = ResultProblem.FromValidation(validationException);
return Result<ProductDisplayModel>.Failure(problem);
}
catch (CatalogNotFoundException)
{
return Result<ProductDisplayModel>.Failure(new ResultProblem
{
Title = "Product not found.",
Detail = $"Product '{normalizedSlug}' was not found.",
StatusCode = StatusCodes.Status404NotFound
});
}
catch (RemoteProblemException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
{
return Result<ProductDisplayModel>.Failure(new ResultProblem
{
Title = "Product not found.",
Detail = $"Product '{normalizedSlug}' was not found.",
StatusCode = StatusCodes.Status404NotFound
});
}
catch (DomainException domainException)
{
var problem = ResultProblem.Unexpected(domainException.Message, domainException, HttpStatusCode.UnprocessableEntity);
return Result<ProductDisplayModel>.Failure(problem);
}
catch (RemoteProblemException ex)
{
var problem = ResultProblem.Unexpected("Failed to retrieve product.", ex, ex.StatusCode);
return Result<ProductDisplayModel>.Failure(problem);
}
catch (Exception exception)
{
var problem = ResultProblem.Unexpected("Failed to retrieve product.", exception);
return Result<ProductDisplayModel>.Failure(problem);
}
}
}
}
internal static class ProductClientExtensions
{
public static Task<GetProductDetail.Response> GetProductConfig(
this IProductClient productClient,
string slug,
CancellationToken cancellationToken) =>
productClient.GetProductDetail(slug, cancellationToken);
}