Latest
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ public class Module : IModule
|
||||
|
||||
builder.Services.AddScoped<ICatalogDbContextFactory, CatalogDbContextFactory>();
|
||||
|
||||
builder.Services.AddHostedService<DataSeeder>();
|
||||
//builder.Services.AddHostedService<DataSeeder>();
|
||||
|
||||
|
||||
builder.Services.AddScoped<INotificationService, NotificationService>();
|
||||
@@ -47,6 +47,7 @@ public class Module : IModule
|
||||
builder.Services.AddScoped<ICategoriesPageService, Categories.PageService>();
|
||||
builder.Services.AddScoped<Home.Service>();
|
||||
builder.Services.AddScoped<Products.ListingService>();
|
||||
builder.Services.AddScoped<Products.DetailService>();
|
||||
builder.Services.AddScoped<IProductListingService>(sp => sp.GetRequiredService<Products.ListingService>());
|
||||
builder.Services.AddScoped<IHomePageService>(sp => sp.GetRequiredService<Home.Service>());
|
||||
|
||||
|
||||
@@ -18,6 +18,14 @@ builder.AddPrefab();
|
||||
var app = builder.Build();
|
||||
|
||||
app.UsePrefab();
|
||||
// if (app.Environment.IsDevelopment())
|
||||
// {
|
||||
// using var scope = app.Services.CreateScope();
|
||||
// var seeder = scope.ServiceProvider.GetRequiredService<Prefab.Catalog.Data.Seeder>();
|
||||
// var db = scope.ServiceProvider.GetRequiredService<Prefab.Catalog.Data.IModuleDb>();
|
||||
// await seeder.Execute(scope.ServiceProvider, CancellationToken.None);
|
||||
// }
|
||||
|
||||
app.MapDefaultEndpoints();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
|
||||
@@ -41,11 +41,11 @@ public static class UrlPolicy
|
||||
/// <summary>
|
||||
/// Builds a canonical catalog URL for navigating to a specific product detail page.
|
||||
/// </summary>
|
||||
/// <param name="productSlug">The slug of the product.</param>
|
||||
public static string BrowseProduct(string productSlug)
|
||||
/// <param name="slug">The slug of the product.</param>
|
||||
public static string BrowseProduct(string slug)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(productSlug);
|
||||
return $"/catalog/product?product-slug={Uri.EscapeDataString(productSlug)}";
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(slug);
|
||||
return $"/browse/product?slug={Uri.EscapeDataString(slug)}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
}
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"PrefabDb": "Server=127.0.0.1,14330;Initial Catalog=prefab-db;User ID=sa;Password=Nq7K3YR2;TrustServerCertificate=True;Encrypt=False;MultipleActiveResultSets=True",
|
||||
"PrefabDbReadOnly": "Server=127.0.0.1,14330;Initial Catalog=prefab-db;User ID=sa;Password=Nq7K3YR2;TrustServerCertificate=True;Encrypt=False;MultipleActiveResultSets=True"
|
||||
"PrefabDb": "Server=127.0.0.1,14331;Initial Catalog=prefab-db;User ID=sa;Password=Nq7K3YR2;TrustServerCertificate=True;Encrypt=False;MultipleActiveResultSets=True",
|
||||
"PrefabDbReadOnly": "Server=127.0.0.1,14331;Initial Catalog=prefab-db;User ID=sa;Password=Nq7K3YR2;TrustServerCertificate=True;Encrypt=False;MultipleActiveResultSets=True"
|
||||
},
|
||||
"Prefab": {
|
||||
"Catalog": {
|
||||
|
||||
Reference in New Issue
Block a user