Files
FrontOffice/src/FrontOffice.Main/Utilities/ProductService.cs
T
masoodafar-web eab978188a Refactor: Update Protobuf references to CMSMicroservice across multiple components
- Changed Protobuf imports from FrontOffice.BFF to CMSMicroservice in Profile components (EditAddressDialog, Index, PaymentCallback, Personal, Settings).
- Updated CheckoutSummary, OrderDetail, OrderTracking, and Orders pages to use new UserOrder Protobuf definitions.
- Refactored WalletService, PackageService, and other utility services to align with new Protobuf structure.
- Adjusted appsettings.json for local development URL.
- Removed unused validators and simplified validation logic in AuthDialog.
- Enhanced ClubMembershipContractDialog to utilize new OtpToken service for OTP handling.
2026-02-03 00:02:16 +03:30

280 lines
8.2 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using CMSMicroservice.Protobuf.Protos.Products;
using Google.Protobuf.WellKnownTypes;
namespace FrontOffice.Main.Utilities;
public record Product(
long Id,
string Title,
string Description,
string FullInformation,
string ImageUrl,
long Price,
int Discount = 1,
int Rate = 0,
int RemainingCount = 0)
{
public IReadOnlyList<ProductGalleryImage> Gallery { get; init; }
= [];
public IReadOnlyList<ProductCategoryPathInfo> Categories { get; init; }
= [];
}
public record ProductGalleryImage(
long ProductGalleryId,
long ProductImageId,
string Title,
string ImageUrl,
string ThumbnailUrl);
public record ProductCategoryNodeInfo(
long Id,
string Title,
long? ParentId);
public record ProductCategoryPathInfo(
long CategoryId,
string Title,
IReadOnlyList<ProductCategoryNodeInfo> Nodes)
{
public string DisplayLabel => string.Join(" ", Nodes.Select(node => node.Title));
public ProductCategoryNodeInfo Leaf => Nodes.Last();
}
public class ProductService
{
private readonly ConcurrentDictionary<long, CacheEntry> _cache = new();
private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(1);
private readonly CMSMicroservice.Protobuf.Protos.Products.ProductsContract.ProductsContractClient _client;
public ProductService(CMSMicroservice.Protobuf.Protos.Products.ProductsContract.ProductsContractClient client)
{
_client = client;
}
public async Task<List<Product>> GetProductsAsync(string? query = null, long? categoryId = null, string? sortBy = null)
{
try
{
var request = new GetAllProductsByFilterRequest
{
Filter = new GetAllProductsByFilterFilter
{
Title = query ?? string.Empty,
Description = query ?? string.Empty,
ShortInfomation = query ?? string.Empty,
FullInformation = query ?? string.Empty
}
};
if (categoryId is { } value)
{
request.Filter.CategoryId = value;
}
if (!string.IsNullOrEmpty(sortBy))
{
request.SortBy = sortBy;
}
var resp = await _client.GetAllProductsByFilterAsync(request);
return MapAndCache(resp.Models);
}
catch
{
IEnumerable<Product> list = GetValidCachedProducts();
if (!string.IsNullOrWhiteSpace(query))
{
var q = query.Trim();
list = list.Where(p =>
p.Title.Contains(q, StringComparison.OrdinalIgnoreCase) ||
p.Description.Contains(q, StringComparison.OrdinalIgnoreCase));
}
return list.OrderBy(p => p.Id).ToList();
}
}
public async Task<Product?> GetByIdAsync(long id)
{
if (TryGetCachedProduct(id, out var cached) && HasDetailedData(cached))
{
return cached;
}
try
{
var resp = await _client.GetProductsAsync(new GetProductsRequest { Id = id });
if (resp == null)
{
return null;
}
return MapAndCache(resp);
}
catch
{
if (cached is not null)
{
return cached;
}
TryGetCachedProduct(id, out var result);
return result;
}
}
private List<Product> MapAndCache(
Google.Protobuf.Collections.RepeatedField<GetAllProductsByFilterResponseModel> models)
{
var list = new List<Product>();
foreach (var m in models)
{
var p = new Product(
Id: m.Id,
Title: m.Title ?? string.Empty,
Description: m.Description ?? string.Empty,
FullInformation: m.FullInformation ?? string.Empty,
ImageUrl: string.IsNullOrWhiteSpace(m.ImagePath) ? string.Empty : UrlUtility.DownloadUrl + m.ImagePath,
Price: m.Price,
Discount: m.Discount,
Rate: m.Rate,
RemainingCount: m.RemainingCount
);
p = PreserveCachedDetails(p);
CacheProduct(p);
list.Add(p);
}
return list;
}
private Product PreserveCachedDetails(Product product)
{
if (_cache.TryGetValue(product.Id, out var entry))
{
var cached = entry.Product;
var hasGallery = cached.Gallery.Count > 0;
var hasCategories = cached.Categories.Count > 0;
if (hasGallery || hasCategories)
{
product = product with
{
Gallery = hasGallery ? cached.Gallery : product.Gallery,
Categories = hasCategories ? cached.Categories : product.Categories
};
}
}
return product;
}
private Product MapAndCache(GetProductsResponse model)
{
var gallery = model.Gallery
.Select(item => new ProductGalleryImage(
ProductGalleryId: item.ProductGalleryId,
ProductImageId: item.ProductImageId,
Title: item.Title ?? string.Empty,
ImageUrl: BuildUrl(item.ImagePath),
ThumbnailUrl: BuildUrl(item.ImageThumbnailPath)))
.ToList();
var product = new Product(
Id: model.Id,
Title: model.Title ?? string.Empty,
Description: model.Description ?? string.Empty,
FullInformation: model.FullInformation ?? string.Empty,
ImageUrl: BuildUrl(model.ImagePath),
Price: model.Price,
Discount: model.Discount,
Rate: model.Rate,
RemainingCount: model.RemainingCount)
{
Gallery = gallery,
Categories = MapCategoryPaths(model.Categories)
};
CacheProduct(product);
return product;
}
private void CacheProduct(Product product)
{
var entry = new CacheEntry(product, DateTime.UtcNow.Add(CacheDuration));
_cache.AddOrUpdate(product.Id, entry, (_, _) => entry);
}
private bool TryGetCachedProduct(long id, [NotNullWhen(true)] out Product? product)
{
if (_cache.TryGetValue(id, out var entry))
{
if (entry.Expiration > DateTime.UtcNow)
{
product = entry.Product;
return true;
}
_cache.TryRemove(id, out _);
}
product = null;
return false;
}
private IEnumerable<Product> GetValidCachedProducts()
{
var now = DateTime.UtcNow;
return _cache.Values
.Where(entry => entry.Expiration > now)
.Select(entry => entry.Product);
}
private static bool HasDetailedData(Product product)
=> product.Gallery.Count > 0 || product.Categories.Count > 0;
private sealed record CacheEntry(Product Product, DateTime Expiration);
private static string BuildUrl(string? path) =>
string.IsNullOrWhiteSpace(path) ? string.Empty : UrlUtility.DownloadUrl + path;
private static IReadOnlyList<ProductCategoryPathInfo> MapCategoryPaths(IEnumerable<ProductCategoryPath>? categories)
{
if (categories is null)
{
return Array.Empty<ProductCategoryPathInfo>();
}
var result = new List<ProductCategoryPathInfo>();
foreach (var category in categories)
{
var nodes = category.Path
.Select(node => new ProductCategoryNodeInfo(
Id: node.Id,
Title: node.Title ?? string.Empty,
ParentId: node.ParentId
))
.ToList();
if (nodes.Count == 0)
{
continue;
}
result.Add(new ProductCategoryPathInfo(
CategoryId: category.CategoryId,
Title: category.Title ?? string.Empty,
Nodes: nodes));
}
return result;
}
}