using System.Net; using FluentValidation; using Prefab.Domain.Exceptions; using Prefab.Endpoints; using Prefab.Shared.Catalog; using Prefab.Shared.Catalog.Products; 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; namespace Prefab.Web.Gateway; public static class Products { public sealed class Endpoints : IEndpointRegistrar { public void MapEndpoints(IEndpointRouteBuilder endpoints) { endpoints.MapGet( "/bff/catalog/categories/{slug}/models", async (ListingService service, string slug, CancellationToken cancellationToken) => { var result = await service.GetCategoryProducts(slug, cancellationToken); var status = result.Problem?.StatusCode ?? (result.IsSuccess ? StatusCodes.Status200OK : StatusCodes.Status500InternalServerError); return Results.Json(result, statusCode: status); }) .WithTags(nameof(Products)); } } public sealed class ListingService(IModuleClient moduleClient) : IProductListingService { public async Task> GetCategoryProducts(string categorySlug, CancellationToken cancellationToken) { ArgumentException.ThrowIfNullOrWhiteSpace(categorySlug); try { var response = await moduleClient.Product.GetCategoryModels( categorySlug.Trim(), page: null, pageSize: null, sort: null, direction: null, cancellationToken); var listing = MapListing(response.Result); return Result.Success(listing); } catch (ValidationException validationException) { var problem = ResultProblem.FromValidation(validationException); return Result.Failure(problem); } catch (DomainException domainException) { var problem = ResultProblem.Unexpected(domainException.Message, domainException, HttpStatusCode.UnprocessableEntity); return Result.Failure(problem); } catch (Exception exception) { var problem = ResultProblem.Unexpected("Failed to retrieve category products.", exception); return Result.Failure(problem); } } private static ProductListingModel MapListing(GetCategoryModels.CategoryProductsResponse payload) { var category = payload.Category; var categoryModel = new ProductCategoryModel { Id = category.Id, Name = category.Name, Slug = category.Slug, Description = category.Description, HeroImageUrl = category.HeroImageUrl, Icon = category.Icon }; var cards = payload.Products .Select(product => new ProductCardModel { Id = product.Id, Title = product.Name, Url = UrlBuilder.BrowseProduct(product.Slug), Slug = product.Slug, PrimaryImageUrl = product.PrimaryImageUrl, CategoryName = category.Name, CategoryUrl = UrlBuilder.BrowseCategory(category.Slug), FromPrice = product.FromPrice.HasValue ? new MoneyModel { Amount = product.FromPrice.Value } : null, IsPriced = product.FromPrice.HasValue, OldPrice = null, IsOnSale = false, Rating = 0, ReviewCount = 0, Badges = new List(), LastModifiedOn = product.LastModifiedOn }) .ToList(); return new ProductListingModel { Category = categoryModel, Products = cards, Total = payload.Total, Page = payload.Page, PageSize = payload.PageSize }; } } }