Features: - Complete .NET client SDK for LittleShop API - JWT authentication with automatic token management - Catalog service for products and categories - Order service with payment creation - Retry policies using Polly for resilience - Error handling middleware - Dependency injection support - Comprehensive documentation and examples SDK Components: - Authentication service with token refresh - Strongly-typed models for all API responses - HTTP handlers for retry and error handling - Extension methods for easy DI registration - Example console application demonstrating usage Test Updates: - Fixed test compilation errors - Updated test data builders for new models - Corrected service constructor dependencies - Fixed enum value changes (PaymentStatus, OrderStatus) Documentation: - Complete project README with features and usage - Client SDK README with detailed examples - API endpoint documentation - Security considerations - Deployment guidelines Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
138 lines
4.9 KiB
C#
138 lines
4.9 KiB
C#
using System.Net.Http.Json;
|
|
using LittleShop.Client.Models;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace LittleShop.Client.Services;
|
|
|
|
public class CatalogService : ICatalogService
|
|
{
|
|
private readonly HttpClient _httpClient;
|
|
private readonly ILogger<CatalogService> _logger;
|
|
|
|
public CatalogService(HttpClient httpClient, ILogger<CatalogService> logger)
|
|
{
|
|
_httpClient = httpClient;
|
|
_logger = logger;
|
|
}
|
|
|
|
public async Task<ApiResponse<List<Category>>> GetCategoriesAsync()
|
|
{
|
|
try
|
|
{
|
|
var response = await _httpClient.GetAsync("api/catalog/categories");
|
|
|
|
if (response.IsSuccessStatusCode)
|
|
{
|
|
var categories = await response.Content.ReadFromJsonAsync<List<Category>>();
|
|
return ApiResponse<List<Category>>.Success(categories ?? new List<Category>());
|
|
}
|
|
|
|
var error = await response.Content.ReadAsStringAsync();
|
|
return ApiResponse<List<Category>>.Failure(error, response.StatusCode);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Failed to get categories");
|
|
return ApiResponse<List<Category>>.Failure(
|
|
ex.Message,
|
|
System.Net.HttpStatusCode.InternalServerError);
|
|
}
|
|
}
|
|
|
|
public async Task<ApiResponse<Category>> GetCategoryByIdAsync(Guid id)
|
|
{
|
|
try
|
|
{
|
|
var response = await _httpClient.GetAsync($"api/catalog/categories/{id}");
|
|
|
|
if (response.IsSuccessStatusCode)
|
|
{
|
|
var category = await response.Content.ReadFromJsonAsync<Category>();
|
|
if (category != null)
|
|
return ApiResponse<Category>.Success(category);
|
|
}
|
|
|
|
var error = await response.Content.ReadAsStringAsync();
|
|
return ApiResponse<Category>.Failure(error, response.StatusCode);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Failed to get category {CategoryId}", id);
|
|
return ApiResponse<Category>.Failure(
|
|
ex.Message,
|
|
System.Net.HttpStatusCode.InternalServerError);
|
|
}
|
|
}
|
|
|
|
public async Task<ApiResponse<PagedResult<Product>>> GetProductsAsync(
|
|
int pageNumber = 1,
|
|
int pageSize = 20,
|
|
Guid? categoryId = null,
|
|
string? searchTerm = null,
|
|
decimal? minPrice = null,
|
|
decimal? maxPrice = null)
|
|
{
|
|
try
|
|
{
|
|
var queryParams = new List<string>
|
|
{
|
|
$"pageNumber={pageNumber}",
|
|
$"pageSize={pageSize}"
|
|
};
|
|
|
|
if (categoryId.HasValue)
|
|
queryParams.Add($"categoryId={categoryId.Value}");
|
|
if (!string.IsNullOrEmpty(searchTerm))
|
|
queryParams.Add($"search={Uri.EscapeDataString(searchTerm)}");
|
|
if (minPrice.HasValue)
|
|
queryParams.Add($"minPrice={minPrice.Value}");
|
|
if (maxPrice.HasValue)
|
|
queryParams.Add($"maxPrice={maxPrice.Value}");
|
|
|
|
var queryString = string.Join("&", queryParams);
|
|
var response = await _httpClient.GetAsync($"api/catalog/products?{queryString}");
|
|
|
|
if (response.IsSuccessStatusCode)
|
|
{
|
|
var result = await response.Content.ReadFromJsonAsync<PagedResult<Product>>();
|
|
if (result != null)
|
|
return ApiResponse<PagedResult<Product>>.Success(result);
|
|
}
|
|
|
|
var error = await response.Content.ReadAsStringAsync();
|
|
return ApiResponse<PagedResult<Product>>.Failure(error, response.StatusCode);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Failed to get products");
|
|
return ApiResponse<PagedResult<Product>>.Failure(
|
|
ex.Message,
|
|
System.Net.HttpStatusCode.InternalServerError);
|
|
}
|
|
}
|
|
|
|
public async Task<ApiResponse<Product>> GetProductByIdAsync(Guid id)
|
|
{
|
|
try
|
|
{
|
|
var response = await _httpClient.GetAsync($"api/catalog/products/{id}");
|
|
|
|
if (response.IsSuccessStatusCode)
|
|
{
|
|
var product = await response.Content.ReadFromJsonAsync<Product>();
|
|
if (product != null)
|
|
return ApiResponse<Product>.Success(product);
|
|
}
|
|
|
|
var error = await response.Content.ReadAsStringAsync();
|
|
return ApiResponse<Product>.Failure(error, response.StatusCode);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Failed to get product {ProductId}", id);
|
|
return ApiResponse<Product>.Failure(
|
|
ex.Message,
|
|
System.Net.HttpStatusCode.InternalServerError);
|
|
}
|
|
}
|
|
} |