327 lines
11 KiB
C#
327 lines
11 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using LittleShop.Data;
|
|
using LittleShop.Models;
|
|
using LittleShop.DTOs;
|
|
|
|
namespace LittleShop.Services;
|
|
|
|
public class ProductService : IProductService
|
|
{
|
|
private readonly LittleShopContext _context;
|
|
private readonly IWebHostEnvironment _environment;
|
|
|
|
public ProductService(LittleShopContext context, IWebHostEnvironment environment)
|
|
{
|
|
_context = context;
|
|
_environment = environment;
|
|
}
|
|
|
|
public async Task<IEnumerable<ProductDto>> GetAllProductsAsync()
|
|
{
|
|
return await _context.Products
|
|
.Include(p => p.Category)
|
|
.Include(p => p.Photos)
|
|
.Where(p => p.IsActive)
|
|
.Select(p => new ProductDto
|
|
{
|
|
Id = p.Id,
|
|
Name = p.Name,
|
|
Description = p.Description,
|
|
Price = p.Price,
|
|
Weight = p.Weight,
|
|
WeightUnit = p.WeightUnit,
|
|
CategoryId = p.CategoryId,
|
|
CategoryName = p.Category.Name,
|
|
CreatedAt = p.CreatedAt,
|
|
UpdatedAt = p.UpdatedAt,
|
|
IsActive = p.IsActive,
|
|
Photos = p.Photos.OrderBy(ph => ph.SortOrder).Select(ph => new ProductPhotoDto
|
|
{
|
|
Id = ph.Id,
|
|
FileName = ph.FileName,
|
|
FilePath = ph.FilePath,
|
|
AltText = ph.AltText,
|
|
SortOrder = ph.SortOrder
|
|
}).ToList()
|
|
})
|
|
.ToListAsync();
|
|
}
|
|
|
|
public async Task<IEnumerable<ProductDto>> GetProductsByCategoryAsync(Guid categoryId)
|
|
{
|
|
return await _context.Products
|
|
.Include(p => p.Category)
|
|
.Include(p => p.Photos)
|
|
.Where(p => p.IsActive && p.CategoryId == categoryId)
|
|
.Select(p => new ProductDto
|
|
{
|
|
Id = p.Id,
|
|
Name = p.Name,
|
|
Description = p.Description,
|
|
Price = p.Price,
|
|
Weight = p.Weight,
|
|
WeightUnit = p.WeightUnit,
|
|
CategoryId = p.CategoryId,
|
|
CategoryName = p.Category.Name,
|
|
CreatedAt = p.CreatedAt,
|
|
UpdatedAt = p.UpdatedAt,
|
|
IsActive = p.IsActive,
|
|
Photos = p.Photos.OrderBy(ph => ph.SortOrder).Select(ph => new ProductPhotoDto
|
|
{
|
|
Id = ph.Id,
|
|
FileName = ph.FileName,
|
|
FilePath = ph.FilePath,
|
|
AltText = ph.AltText,
|
|
SortOrder = ph.SortOrder
|
|
}).ToList()
|
|
})
|
|
.ToListAsync();
|
|
}
|
|
|
|
public async Task<ProductDto?> GetProductByIdAsync(Guid id)
|
|
{
|
|
var product = await _context.Products
|
|
.Include(p => p.Category)
|
|
.Include(p => p.Photos)
|
|
.FirstOrDefaultAsync(p => p.Id == id);
|
|
|
|
if (product == null) return null;
|
|
|
|
return new ProductDto
|
|
{
|
|
Id = product.Id,
|
|
Name = product.Name,
|
|
Description = product.Description,
|
|
Price = product.Price,
|
|
Weight = product.Weight,
|
|
WeightUnit = product.WeightUnit,
|
|
StockQuantity = product.StockQuantity,
|
|
CategoryId = product.CategoryId,
|
|
CategoryName = product.Category.Name,
|
|
CreatedAt = product.CreatedAt,
|
|
UpdatedAt = product.UpdatedAt,
|
|
IsActive = product.IsActive,
|
|
Photos = product.Photos.OrderBy(ph => ph.SortOrder).Select(ph => new ProductPhotoDto
|
|
{
|
|
Id = ph.Id,
|
|
FileName = ph.FileName,
|
|
FilePath = ph.FilePath,
|
|
AltText = ph.AltText,
|
|
SortOrder = ph.SortOrder
|
|
}).ToList()
|
|
};
|
|
}
|
|
|
|
public async Task<ProductDto> CreateProductAsync(CreateProductDto createProductDto)
|
|
{
|
|
var product = new Product
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Name = createProductDto.Name,
|
|
Description = string.IsNullOrEmpty(createProductDto.Description) ? " " : createProductDto.Description,
|
|
Price = createProductDto.Price,
|
|
Weight = createProductDto.Weight,
|
|
WeightUnit = createProductDto.WeightUnit,
|
|
StockQuantity = createProductDto.StockQuantity,
|
|
CategoryId = createProductDto.CategoryId,
|
|
CreatedAt = DateTime.UtcNow,
|
|
UpdatedAt = DateTime.UtcNow,
|
|
IsActive = true
|
|
};
|
|
|
|
_context.Products.Add(product);
|
|
await _context.SaveChangesAsync();
|
|
|
|
var category = await _context.Categories.FindAsync(createProductDto.CategoryId);
|
|
|
|
return new ProductDto
|
|
{
|
|
Id = product.Id,
|
|
Name = product.Name,
|
|
Description = product.Description,
|
|
Price = product.Price,
|
|
Weight = product.Weight,
|
|
WeightUnit = product.WeightUnit,
|
|
CategoryId = product.CategoryId,
|
|
CategoryName = category?.Name ?? "",
|
|
CreatedAt = product.CreatedAt,
|
|
UpdatedAt = product.UpdatedAt,
|
|
IsActive = product.IsActive,
|
|
Photos = new List<ProductPhotoDto>()
|
|
};
|
|
}
|
|
|
|
public async Task<bool> UpdateProductAsync(Guid id, UpdateProductDto updateProductDto)
|
|
{
|
|
var product = await _context.Products.FindAsync(id);
|
|
if (product == null) return false;
|
|
|
|
if (!string.IsNullOrEmpty(updateProductDto.Name))
|
|
product.Name = updateProductDto.Name;
|
|
|
|
if (!string.IsNullOrEmpty(updateProductDto.Description))
|
|
product.Description = updateProductDto.Description;
|
|
|
|
if (updateProductDto.Price.HasValue)
|
|
product.Price = updateProductDto.Price.Value;
|
|
|
|
if (updateProductDto.Weight.HasValue)
|
|
product.Weight = updateProductDto.Weight.Value;
|
|
|
|
if (updateProductDto.WeightUnit.HasValue)
|
|
product.WeightUnit = updateProductDto.WeightUnit.Value;
|
|
|
|
if (updateProductDto.StockQuantity.HasValue)
|
|
product.StockQuantity = updateProductDto.StockQuantity.Value;
|
|
|
|
if (updateProductDto.CategoryId.HasValue)
|
|
product.CategoryId = updateProductDto.CategoryId.Value;
|
|
|
|
if (updateProductDto.IsActive.HasValue)
|
|
product.IsActive = updateProductDto.IsActive.Value;
|
|
|
|
product.UpdatedAt = DateTime.UtcNow;
|
|
|
|
await _context.SaveChangesAsync();
|
|
return true;
|
|
}
|
|
|
|
public async Task<bool> DeleteProductAsync(Guid id)
|
|
{
|
|
var product = await _context.Products.FindAsync(id);
|
|
if (product == null) return false;
|
|
|
|
product.IsActive = false;
|
|
await _context.SaveChangesAsync();
|
|
return true;
|
|
}
|
|
|
|
public async Task<bool> AddProductPhotoAsync(Guid productId, IFormFile file, string? altText = null)
|
|
{
|
|
var product = await _context.Products.FindAsync(productId);
|
|
if (product == null) return false;
|
|
|
|
var uploadsPath = Path.Combine(_environment.WebRootPath ?? "wwwroot", "uploads", "products");
|
|
Directory.CreateDirectory(uploadsPath);
|
|
|
|
var fileName = $"{Guid.NewGuid()}_{file.FileName}";
|
|
var filePath = Path.Combine(uploadsPath, fileName);
|
|
|
|
using (var stream = new FileStream(filePath, FileMode.Create))
|
|
{
|
|
await file.CopyToAsync(stream);
|
|
}
|
|
|
|
var maxSortOrder = await _context.ProductPhotos
|
|
.Where(pp => pp.ProductId == productId)
|
|
.Select(pp => (int?)pp.SortOrder)
|
|
.MaxAsync() ?? 0;
|
|
|
|
var productPhoto = new ProductPhoto
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
ProductId = productId,
|
|
FileName = fileName,
|
|
FilePath = $"/uploads/products/{fileName}",
|
|
AltText = altText,
|
|
SortOrder = maxSortOrder + 1,
|
|
CreatedAt = DateTime.UtcNow
|
|
};
|
|
|
|
_context.ProductPhotos.Add(productPhoto);
|
|
await _context.SaveChangesAsync();
|
|
return true;
|
|
}
|
|
|
|
public async Task<bool> RemoveProductPhotoAsync(Guid productId, Guid photoId)
|
|
{
|
|
var photo = await _context.ProductPhotos
|
|
.FirstOrDefaultAsync(pp => pp.Id == photoId && pp.ProductId == productId);
|
|
|
|
if (photo == null) return false;
|
|
|
|
var physicalPath = Path.Combine(_environment.WebRootPath, photo.FilePath.TrimStart('/'));
|
|
if (File.Exists(physicalPath))
|
|
{
|
|
File.Delete(physicalPath);
|
|
}
|
|
|
|
_context.ProductPhotos.Remove(photo);
|
|
await _context.SaveChangesAsync();
|
|
return true;
|
|
}
|
|
|
|
public async Task<ProductPhotoDto?> AddProductPhotoAsync(CreateProductPhotoDto photoDto)
|
|
{
|
|
var product = await _context.Products.FindAsync(photoDto.ProductId);
|
|
if (product == null) return null;
|
|
|
|
var maxSortOrder = await _context.ProductPhotos
|
|
.Where(pp => pp.ProductId == photoDto.ProductId)
|
|
.Select(pp => pp.SortOrder)
|
|
.DefaultIfEmpty(0)
|
|
.MaxAsync();
|
|
|
|
var productPhoto = new ProductPhoto
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
ProductId = photoDto.ProductId,
|
|
FileName = Path.GetFileName(photoDto.PhotoUrl),
|
|
FilePath = photoDto.PhotoUrl,
|
|
AltText = photoDto.AltText,
|
|
SortOrder = photoDto.DisplayOrder > 0 ? photoDto.DisplayOrder : maxSortOrder + 1,
|
|
CreatedAt = DateTime.UtcNow
|
|
};
|
|
|
|
_context.ProductPhotos.Add(productPhoto);
|
|
await _context.SaveChangesAsync();
|
|
|
|
return new ProductPhotoDto
|
|
{
|
|
Id = productPhoto.Id,
|
|
FileName = productPhoto.FileName,
|
|
FilePath = productPhoto.FilePath,
|
|
AltText = productPhoto.AltText,
|
|
SortOrder = productPhoto.SortOrder
|
|
};
|
|
}
|
|
|
|
public async Task<IEnumerable<ProductDto>> SearchProductsAsync(string searchTerm)
|
|
{
|
|
var query = _context.Products
|
|
.Include(p => p.Category)
|
|
.Include(p => p.Photos)
|
|
.Where(p => p.IsActive);
|
|
|
|
if (!string.IsNullOrWhiteSpace(searchTerm))
|
|
{
|
|
searchTerm = searchTerm.ToLower();
|
|
query = query.Where(p =>
|
|
p.Name.ToLower().Contains(searchTerm) ||
|
|
p.Description.ToLower().Contains(searchTerm));
|
|
}
|
|
|
|
return await query.Select(p => new ProductDto
|
|
{
|
|
Id = p.Id,
|
|
Name = p.Name,
|
|
Description = p.Description,
|
|
Price = p.Price,
|
|
Weight = p.Weight,
|
|
WeightUnit = p.WeightUnit,
|
|
CategoryId = p.CategoryId,
|
|
CategoryName = p.Category.Name,
|
|
CreatedAt = p.CreatedAt,
|
|
UpdatedAt = p.UpdatedAt,
|
|
IsActive = p.IsActive,
|
|
Photos = p.Photos.OrderBy(ph => ph.SortOrder).Select(ph => new ProductPhotoDto
|
|
{
|
|
Id = ph.Id,
|
|
FileName = ph.FileName,
|
|
FilePath = ph.FilePath,
|
|
AltText = ph.AltText,
|
|
SortOrder = ph.SortOrder
|
|
}).ToList()
|
|
}).ToListAsync();
|
|
}
|
|
} |