This commit is contained in:
@@ -0,0 +1,277 @@
|
||||
using LiteCharms.Features.TechShop.Extensions;
|
||||
using LiteCharms.Features.TechShop.Postgres;
|
||||
using LiteCharms.Features.TechShop.Products.Models;
|
||||
|
||||
namespace LiteCharms.Features.TechShop.Products;
|
||||
|
||||
public class ProductService(IDbContextFactory<ShopDbContext> contextFactory)
|
||||
{
|
||||
public async ValueTask<Result> ChangeProductPriceStatusAsync(Guid productPriceId, bool active, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var price = await context.ProductPrices.FirstOrDefaultAsync(p => p.Id == productPriceId, cancellationToken);
|
||||
|
||||
if (price is null)
|
||||
return Result.Fail($"Could not find product price with ID {productPriceId}");
|
||||
|
||||
price.Active = active;
|
||||
price.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||
? Result.Ok()
|
||||
: Result.Fail($"Failed to change product price by ID {productPriceId}");
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask<Result> ChangeProductStatusAsync(Guid productId, bool active, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var product = await context.Products.FirstOrDefaultAsync(p => p.Id == productId, cancellationToken);
|
||||
|
||||
if (product is null)
|
||||
return Result.Fail($"Could not find product with ID {productId}");
|
||||
|
||||
product.Active = active;
|
||||
|
||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||
? Result.Ok()
|
||||
: Result.Fail($"Failed to change product status by ID {productId}");
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask<Result<Guid>> CreateProductAsync(CreateProduct request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
if (!await context.Products.AnyAsync(p => p.Name == request.Name, cancellationToken))
|
||||
return Result.Fail<Guid>($"A product by the same name '{request.Name}' already exists");
|
||||
|
||||
var newProduct = context.Products.Add(new Entities.Product
|
||||
{
|
||||
Name = request.Name,
|
||||
Summary = request.Summary,
|
||||
Description = request.Description,
|
||||
ImageUrl = request.ImageUrl,
|
||||
Thumbnails = request.Thumbnails,
|
||||
Metadata = request.Metadata
|
||||
});
|
||||
|
||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||
? Result.Ok(newProduct.Entity.Id)
|
||||
: Result.Fail($"Failed to create new product '{request.Name}'");
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask<Result<Guid>> CreateProductPriceAsync(Guid productId, decimal price, decimal discount = 0, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var newProductPrice = context.ProductPrices.Add(new Entities.ProductPrice
|
||||
{
|
||||
Price = price,
|
||||
Discount = discount,
|
||||
ProductId = productId
|
||||
});
|
||||
|
||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||
? Result.Ok(newProductPrice.Entity.Id)
|
||||
: Result.Fail($"Failed to create new product price for product id {productId}");
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask<Result<Product>> GetProductAsync(Guid productId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var product = await context.Products.FirstOrDefaultAsync(p => p.Id == productId, cancellationToken);
|
||||
|
||||
return product is not null
|
||||
? Result.Ok(product.ToModel())
|
||||
: Result.Fail<Product>(new Error($"Product with ID {productId} not found."));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result.Fail<Product>(new Error(ex.Message).CausedBy(ex));
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask<Result<ProductPrice>> GetProductPriceAsync(Guid productPriceId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
if (!await context.Products.AnyAsync(p => p.Id == productPriceId, cancellationToken))
|
||||
return Result.Fail<ProductPrice>(new Error($"Product {productPriceId} not found."));
|
||||
|
||||
var productPrice = await context.ProductPrices.AsNoTracking()
|
||||
.OrderByDescending(pp => pp.CreatedAt)
|
||||
.FirstOrDefaultAsync(pp => pp.Id == productPriceId, cancellationToken);
|
||||
|
||||
return productPrice is not null
|
||||
? Result.Ok(productPrice.ToModel())
|
||||
: Result.Fail<ProductPrice>(new Error($"Product price {productPriceId} not found."));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result.Fail<ProductPrice>(new Error(ex.Message).CausedBy(ex));
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask<Result<ProductPrice[]>> GetProductPricesAsync(int maxRecords = 1000, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var products = await context.ProductPrices.AsNoTracking()
|
||||
.OrderByDescending(o => o.Id)
|
||||
.Take(maxRecords)
|
||||
.ToArrayAsync(cancellationToken);
|
||||
|
||||
return Result.Ok(products.Select(p => p.ToModel()).ToArray());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result.Fail<ProductPrice[]>(new Error(ex.Message).CausedBy(ex));
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask<Result<Product[]>> GetProductsAsync(int maxRecords = 1000, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var products = await context.Products.AsNoTracking()
|
||||
.OrderByDescending(o => o.Id)
|
||||
.Take(maxRecords)
|
||||
.ToArrayAsync(cancellationToken);
|
||||
|
||||
return Result.Ok(products.Select(p => p.ToModel()).ToArray());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result.Fail<Product[]>(new Error(ex.Message).CausedBy(ex));
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask<Result<Guid>> ReplaceProductPriceAsync(Guid productPriceId, decimal price, decimal discount = 0, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var existingPrice = await context.ProductPrices.FirstOrDefaultAsync(p => p.Id == productPriceId, cancellationToken);
|
||||
|
||||
if (existingPrice is null)
|
||||
return Result.Fail($"Could not find product price with ID {productPriceId}");
|
||||
|
||||
existingPrice.Active = false;
|
||||
existingPrice.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
if (!(await context.SaveChangesAsync(cancellationToken) > 0))
|
||||
return Result.Fail<Guid>($"Failed to deactivate existing price of ID {productPriceId}, try again later");
|
||||
|
||||
var result = await CreateProductPriceAsync(existingPrice.ProductId, price, discount, cancellationToken);
|
||||
|
||||
if (result.IsFailed)
|
||||
{
|
||||
var deactivatedPrice = await context.ProductPrices.FirstOrDefaultAsync(p => p.Id == productPriceId, cancellationToken);
|
||||
|
||||
existingPrice.Active = true;
|
||||
existingPrice.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||
? Result.Fail<Guid>("Reverted to old price, creation of new price failed")
|
||||
: Result.Fail<Guid>($"Failed to reactivate price of ID {productPriceId} after new price creation failed");
|
||||
}
|
||||
|
||||
return Result.Ok(result.Value);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask<Result> SetProductPriceStatusAsync(Guid productPriceId, bool active, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var productPrice = await context.ProductPrices.FirstOrDefaultAsync(p => p.Id == productPriceId, cancellationToken);
|
||||
|
||||
if (productPrice is null)
|
||||
return Result.Fail($"Could not find product price with ID {productPriceId}");
|
||||
|
||||
productPrice.Active = active;
|
||||
productPrice.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||
? Result.Ok()
|
||||
: Result.Fail($"Failed to change product price status by ID {productPriceId}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask<Result> UpdateProductMetadataAsync(Guid productId, ProductMetadata metadata, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var product = await context.Products.FirstOrDefaultAsync(p => p.Id == productId, cancellationToken);
|
||||
|
||||
if (product is null)
|
||||
return Result.Fail($"Could not find product with ID {productId}");
|
||||
|
||||
product.Metadata = metadata;
|
||||
product.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||
? Result.Ok()
|
||||
: Result.Fail($"Failed to update product metadata by ID {productId}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user