diff --git a/eCommerceApi.dsills735/.gitattributes b/eCommerceApi.dsills735/.gitattributes new file mode 100644 index 00000000..1ff0c423 --- /dev/null +++ b/eCommerceApi.dsills735/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/eCommerceApi.dsills735/.gitignore b/eCommerceApi.dsills735/.gitignore new file mode 100644 index 00000000..9491a2fd --- /dev/null +++ b/eCommerceApi.dsills735/.gitignore @@ -0,0 +1,363 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Oo]ut/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd \ No newline at end of file diff --git a/eCommerceApi.dsills735/Sills.GolfShop.eCommerce.slnx b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce.slnx new file mode 100644 index 00000000..b355a29d --- /dev/null +++ b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce.slnx @@ -0,0 +1,3 @@ + + + diff --git a/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Controllers/CategoryController.cs b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Controllers/CategoryController.cs new file mode 100644 index 00000000..04df079b --- /dev/null +++ b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Controllers/CategoryController.cs @@ -0,0 +1,73 @@ +using Microsoft.AspNetCore.Mvc; +using Sills.GolfShop.eCommerceAPI.Models; +using Sills.GolfShop.eCommerceAPI.Services; +using Sills.GolfShop.eCommerceAPI.Helpers; +using Microsoft.EntityFrameworkCore; + +namespace Sills.GolfShop.eCommerceAPI.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class CategoryController(ICategoryService categoryService) : ControllerBase + +{ + private readonly ICategoryService _categoryService = categoryService; + + [HttpGet] + public async Task>> GetAllCategories([FromQuery] PaginationParameters param, [FromQuery] CategoryParameters categoryParams) + { + var query = _categoryService.GetAllCategoriesQuery(); + query = categoryParams.sortBy switch + { + "name_desc" => query.OrderByDescending(p => p.Name), + _ => query.OrderBy(p => p.Name) + }; + + var pagedCategories = await _categoryService + .GetPagedCategoriesAsync(param.PageNumber, param.PageSize); + + return Ok(pagedCategories); + } + + [HttpGet("{id}")] + public async Task> GetCategoryById(int id) + { + var category = await _categoryService.GetCategoryByIdAsync(id); + if (category == null) + { + return NotFound(); + } + return Ok(category); + } + + [HttpPost] + public async Task> CreateCategory(Categories category) + { + var createdCategory = await _categoryService.CreateCategoryAsync(category); + return CreatedAtAction(nameof(GetCategoryById), new { id = createdCategory.Id }, createdCategory); + } + + [HttpPut("{id}")] + public async Task UpdateCategory(int id, Categories category) + { + var existingCategory = await _categoryService.GetCategoryByIdAsync(id); + if (existingCategory == null) + { + return NotFound(); + } + await _categoryService.UpdateCategoryAsync(id, category); + return NoContent(); + } + [HttpDelete("{id}")] + public async Task DeleteCategory(int id) + { + var existingCategory = await _categoryService.GetCategoryByIdAsync(id); + if (existingCategory == null) + { + return NotFound(); + } + await _categoryService.DeleteCategoryAsync(id); + return NoContent(); + } + +} diff --git a/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Controllers/ProductController.cs b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Controllers/ProductController.cs new file mode 100644 index 00000000..8dfccb6d --- /dev/null +++ b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Controllers/ProductController.cs @@ -0,0 +1,83 @@ +using Microsoft.AspNetCore.Mvc; +using Sills.GolfShop.eCommerceAPI.DTO; +using Sills.GolfShop.eCommerceAPI.Helpers; +using Sills.GolfShop.eCommerceAPI.Models; +using Sills.GolfShop.eCommerceAPI.Services; + +namespace Sills.GolfShop.eCommerceAPI.Controllers; + +[ApiController] +[Route("api/[controller]")] + +public class ProductController(IProductsService productsService) : ControllerBase +{ + private readonly IProductsService _productService = productsService; + + [HttpGet] + public async Task>> GetAllProducts([FromQuery]PaginationParameters param) + { + var products = await _productService.GetAllProductsAsync(); + + var pagedProducts = products + .Skip((param.PageNumber - 1) * param.PageSize) + .Take(param.PageSize) + .ToList(); + + return Ok(pagedProducts); + } + + [HttpGet] + public async Task>> GetAllProductsAsync([FromQuery] PaginationParameters param) + { + var pagedProducts = await _productService + .GetPagedProductsAsync(param.PageNumber, param.PageSize); + + return Ok(pagedProducts); + } + + [HttpGet("{id}")] + public async Task> GetProductById(int id) + { + var product = await _productService.GetProductByIdAsync(id); + if (product == null) + { + return NotFound(); + } + return Ok(product); + } + [HttpPost] + public async Task> CreateProduct(Product product) + { + var createdProduct = await _productService.CreateProductAsync(product); + return CreatedAtAction(nameof(GetProductById), new { id = createdProduct.Id }, createdProduct); + } + + [HttpPut("{id}")] + public async Task UpdateProduct(int id, ProductUpdateDto productUpdateDto) + { + var existingProduct = await _productService.GetProductByIdAsync(id); + if (existingProduct == null) + { + return NotFound(); + } + existingProduct.Name = productUpdateDto.Name; + existingProduct.Description = productUpdateDto.Description; + existingProduct.QuantityInStock = productUpdateDto.QuantityInStock; + + await _productService.UpdateProductAsync(id, existingProduct); + return NoContent(); + } + + [HttpDelete("{id}")] + public async Task DeleteProduct(int id) + { + var existingProduct = await _productService.GetProductByIdAsync(id); + if (existingProduct == null) + { + return NotFound(); + } + await _productService.DeleteProductAsync(id); + + return NoContent(); + } +} diff --git a/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Controllers/SalesController.cs b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Controllers/SalesController.cs new file mode 100644 index 00000000..3c2c4eb9 --- /dev/null +++ b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Controllers/SalesController.cs @@ -0,0 +1,61 @@ +using Sills.GolfShop.eCommerceAPI.Services; +using Microsoft.AspNetCore.Mvc; +using Sills.GolfShop.eCommerceAPI.Models; +using Sills.GolfShop.eCommerceAPI.Helpers; + +namespace Sills.GolfShop.eCommerceAPI.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class SalesController(ISalesService salesService) : ControllerBase +{ + private readonly ISalesService _salesService = salesService; + + [HttpGet] + public async Task>> GetAllSales([FromQuery] PaginationParameters param) + { + var pagedSales = await _salesService + .GetPagedSalesAsync(param.PageNumber, param.PageSize); + return Ok(pagedSales); + } + [HttpGet("{id}")] + public async Task> GetSaleById(int id) + { + var sale = await _salesService.GetSaleByIdAsync(id); + if (sale == null) + { + return NotFound(); + } + return Ok(sale); + } + [HttpPost] + public async Task> CreateSale(Sales sale) + { + var createdSale = await _salesService.CreateSaleAsync(sale); + return CreatedAtAction(nameof(GetSaleById), new { id = createdSale.Id }, createdSale); + } + [HttpPut("{id}")] + public async Task UpdateSale(int id, Sales sale) + { + var existingSale = await _salesService.GetSaleByIdAsync(id); + if (existingSale == null) + { + return NotFound(); + } + await _salesService.UpdateSaleAsync(id, sale); + return NoContent(); + } + + [HttpDelete("{id}")] + public async Task DeleteSale(int id) + { + var existingSale = await _salesService.GetSaleByIdAsync(id); + if (existingSale == null) + { + return NotFound(); + } + await _salesService.DeleteSaleAsync(id); + return NoContent(); + } + +} diff --git a/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/DTO/ProductUpdateDTO.cs b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/DTO/ProductUpdateDTO.cs new file mode 100644 index 00000000..667c7d70 --- /dev/null +++ b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/DTO/ProductUpdateDTO.cs @@ -0,0 +1,3 @@ +namespace Sills.GolfShop.eCommerceAPI.DTO; + +public sealed record ProductUpdateDto(string Name, string Description, int QuantityInStock); diff --git a/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Data/GolfShopDbContext.cs b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Data/GolfShopDbContext.cs new file mode 100644 index 00000000..cf0b3458 --- /dev/null +++ b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Data/GolfShopDbContext.cs @@ -0,0 +1,24 @@ +using Microsoft.EntityFrameworkCore; +using Sills.GolfShop.eCommerceAPI.Models; + +namespace Sills.GolfShop.eCommerceAPI.Data; + +public class GolfShopDbContext(DbContextOptions options) : DbContext(options) +{ + + public DbSet Products { get; set; } + public DbSet Categories { get; set; } + public DbSet Sales { get; set; } + public DbSet ProductSales { get; set; } + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasKey(ps => new { ps.ProductID, ps.SaleID }); + + modelBuilder.Entity() + .Property(p => p.Price) + .HasColumnType("decimal(18,2)"); + } +} + + diff --git a/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Helpers/PaginationParameters.cs b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Helpers/PaginationParameters.cs new file mode 100644 index 00000000..93611620 --- /dev/null +++ b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Helpers/PaginationParameters.cs @@ -0,0 +1,14 @@ +namespace Sills.GolfShop.eCommerceAPI.Helpers; + +public class PaginationParameters +{ + public const int MaxPageSize = 50; + public int PageNumber { get; set; } = 1; + + private int _pageSize = 10; + public int PageSize + { + get => _pageSize; + set => _pageSize = (value > MaxPageSize) ? MaxPageSize : value; + } +} diff --git a/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Helpers/SortingParameters.cs b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Helpers/SortingParameters.cs new file mode 100644 index 00000000..29734935 --- /dev/null +++ b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Helpers/SortingParameters.cs @@ -0,0 +1,20 @@ +namespace Sills.GolfShop.eCommerceAPI.Helpers; + +public class ProductParameters +{ + public string? name { get; set; } + public decimal? minPrice { get; set; } + public decimal? maxPrice { get; set; } + + public string? sortBy { get; set; } = null; +} + +public class CategoryParameters +{ + public string? name { get; set; } + public string? sortBy { get; set; } = null; +} + public class SalesParameters +{ + public string? sortBy { get; set; } = null; +} diff --git a/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Mapping/ProductMapper.cs b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Mapping/ProductMapper.cs new file mode 100644 index 00000000..280037cc --- /dev/null +++ b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Mapping/ProductMapper.cs @@ -0,0 +1,15 @@ +namespace Sills.GolfShop.eCommerceAPI.Mapping; + +public static class ProductMapper +{ + public static DTO.ProductUpdateDto? ToDTO(this Models.Product product) + { + if (product == null) return null; + + return new DTO.ProductUpdateDto( + product.Name, + product.Description, + product.QuantityInStock + ); + } +} diff --git a/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Migrations/20260403232616_initial.Designer.cs b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Migrations/20260403232616_initial.Designer.cs new file mode 100644 index 00000000..da26d3ec --- /dev/null +++ b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Migrations/20260403232616_initial.Designer.cs @@ -0,0 +1,152 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Sills.GolfShop.eCommerceAPI.Data; + +#nullable disable + +namespace Sills.GolfShop.eCommerceAPI.Migrations +{ + [DbContext(typeof(GolfShopDbContext))] + [Migration("20260403232616_initial")] + partial class initial + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Sills.GolfShop.eCommerceAPI.Models.Categories", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("Sills.GolfShop.eCommerceAPI.Models.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CategoryId") + .HasColumnType("int"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.Property("QuantityInStock") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Products"); + }); + + modelBuilder.Entity("Sills.GolfShop.eCommerceAPI.Models.ProductSales", b => + { + b.Property("ProductID") + .HasColumnType("int"); + + b.Property("SaleID") + .HasColumnType("int"); + + b.HasKey("ProductID", "SaleID"); + + b.HasIndex("SaleID"); + + b.ToTable("ProductSales"); + }); + + modelBuilder.Entity("Sills.GolfShop.eCommerceAPI.Models.Sales", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("customerName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("shippingAddress") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Sales"); + }); + + modelBuilder.Entity("Sills.GolfShop.eCommerceAPI.Models.ProductSales", b => + { + b.HasOne("Sills.GolfShop.eCommerceAPI.Models.Product", "Product") + .WithMany("ProductSales") + .HasForeignKey("ProductID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Sills.GolfShop.eCommerceAPI.Models.Sales", "Sale") + .WithMany("ProductSales") + .HasForeignKey("SaleID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + + b.Navigation("Sale"); + }); + + modelBuilder.Entity("Sills.GolfShop.eCommerceAPI.Models.Product", b => + { + b.Navigation("ProductSales"); + }); + + modelBuilder.Entity("Sills.GolfShop.eCommerceAPI.Models.Sales", b => + { + b.Navigation("ProductSales"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Migrations/20260403232616_initial.cs b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Migrations/20260403232616_initial.cs new file mode 100644 index 00000000..26705a90 --- /dev/null +++ b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Migrations/20260403232616_initial.cs @@ -0,0 +1,107 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Sills.GolfShop.eCommerceAPI.Migrations +{ + /// + public partial class initial : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Categories", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column(type: "nvarchar(max)", nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: false), + DeletedAt = table.Column(type: "datetime2", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Categories", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Products", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column(type: "nvarchar(max)", nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: false), + Price = table.Column(type: "decimal(18,2)", nullable: false), + QuantityInStock = table.Column(type: "int", nullable: false), + DeletedAt = table.Column(type: "datetime2", nullable: true), + CategoryId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Products", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Sales", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + customerName = table.Column(type: "nvarchar(max)", nullable: false), + shippingAddress = table.Column(type: "nvarchar(max)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Sales", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ProductSales", + columns: table => new + { + ProductID = table.Column(type: "int", nullable: false), + SaleID = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ProductSales", x => new { x.ProductID, x.SaleID }); + table.ForeignKey( + name: "FK_ProductSales_Products_ProductID", + column: x => x.ProductID, + principalTable: "Products", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ProductSales_Sales_SaleID", + column: x => x.SaleID, + principalTable: "Sales", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_ProductSales_SaleID", + table: "ProductSales", + column: "SaleID"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Categories"); + + migrationBuilder.DropTable( + name: "ProductSales"); + + migrationBuilder.DropTable( + name: "Products"); + + migrationBuilder.DropTable( + name: "Sales"); + } + } +} diff --git a/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Migrations/GolfShopDbContextModelSnapshot.cs b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Migrations/GolfShopDbContextModelSnapshot.cs new file mode 100644 index 00000000..7bbf9761 --- /dev/null +++ b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Migrations/GolfShopDbContextModelSnapshot.cs @@ -0,0 +1,149 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Sills.GolfShop.eCommerceAPI.Data; + +#nullable disable + +namespace Sills.GolfShop.eCommerceAPI.Migrations +{ + [DbContext(typeof(GolfShopDbContext))] + partial class GolfShopDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Sills.GolfShop.eCommerceAPI.Models.Categories", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("Sills.GolfShop.eCommerceAPI.Models.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CategoryId") + .HasColumnType("int"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.Property("QuantityInStock") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Products"); + }); + + modelBuilder.Entity("Sills.GolfShop.eCommerceAPI.Models.ProductSales", b => + { + b.Property("ProductID") + .HasColumnType("int"); + + b.Property("SaleID") + .HasColumnType("int"); + + b.HasKey("ProductID", "SaleID"); + + b.HasIndex("SaleID"); + + b.ToTable("ProductSales"); + }); + + modelBuilder.Entity("Sills.GolfShop.eCommerceAPI.Models.Sales", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("customerName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("shippingAddress") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Sales"); + }); + + modelBuilder.Entity("Sills.GolfShop.eCommerceAPI.Models.ProductSales", b => + { + b.HasOne("Sills.GolfShop.eCommerceAPI.Models.Product", "Product") + .WithMany("ProductSales") + .HasForeignKey("ProductID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Sills.GolfShop.eCommerceAPI.Models.Sales", "Sale") + .WithMany("ProductSales") + .HasForeignKey("SaleID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + + b.Navigation("Sale"); + }); + + modelBuilder.Entity("Sills.GolfShop.eCommerceAPI.Models.Product", b => + { + b.Navigation("ProductSales"); + }); + + modelBuilder.Entity("Sills.GolfShop.eCommerceAPI.Models.Sales", b => + { + b.Navigation("ProductSales"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Models/Categories.cs b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Models/Categories.cs new file mode 100644 index 00000000..d2073c4c --- /dev/null +++ b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Models/Categories.cs @@ -0,0 +1,11 @@ +namespace Sills.GolfShop.eCommerceAPI.Models +{ + public class Categories + { + public int Id { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public DateTime? DeletedAt { get; set; } + + } +} diff --git a/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Models/Product.cs b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Models/Product.cs new file mode 100644 index 00000000..ac22a653 --- /dev/null +++ b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Models/Product.cs @@ -0,0 +1,14 @@ +namespace Sills.GolfShop.eCommerceAPI.Models; + +public class Product +{ + public int Id { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public decimal Price { get; set; } + public int QuantityInStock { get; set; } + public DateTime? DeletedAt { get; set; } + + public List ProductSales { get; } = []; + public int CategoryId { get; internal set; } +} diff --git a/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Models/ProductSales.cs b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Models/ProductSales.cs new file mode 100644 index 00000000..09f7e23d --- /dev/null +++ b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Models/ProductSales.cs @@ -0,0 +1,13 @@ +using Microsoft.EntityFrameworkCore; + +namespace Sills.GolfShop.eCommerceAPI.Models; + +public class ProductSales +{ + public int ProductID { get; set; } + public int SaleID { get; set; } + public Product Product { get; set; } = null!; + public Sales Sale { get; set; } = null!; + + +} diff --git a/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Models/Sales.cs b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Models/Sales.cs new file mode 100644 index 00000000..afccd6eb --- /dev/null +++ b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Models/Sales.cs @@ -0,0 +1,11 @@ +namespace Sills.GolfShop.eCommerceAPI.Models; + +public class Sales +{ + public int Id { get; set; } + public string customerName { get; set; } + + public string shippingAddress { get; set; } = string.Empty; + + public List ProductSales { get; } = []; +} diff --git a/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Postman_Collection/PostmanCollection.json b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Postman_Collection/PostmanCollection.json new file mode 100644 index 00000000..a92647c7 --- /dev/null +++ b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Postman_Collection/PostmanCollection.json @@ -0,0 +1,241 @@ +{ + "info": { + "name": "Sills Golf Shop eCommerce API (Updated)", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "Products", + "item": [ + { + "name": "Get All Products (Paged)", + "request": { + "method": "GET", + "url": { + "raw": "{{baseUrl}}/api/Product?pageNumber=1&pageSize=10", + "host": [ "{{baseUrl}}" ], + "path": [ "api", "Product" ], + "query": [ + { + "key": "pageNumber", + "value": "1" + }, + { + "key": "pageSize", + "value": "10" + } + ] + } + } + }, + { + "name": "Get Product by Id", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/Product/1" + } + }, + { + "name": "Create Product", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"Driver\",\n \"description\": \"Titanium face driver\",\n \"price\": 499.99,\n \"quantityInStock\": 25,\n \"categoryId\": 1\n}" + }, + "url": "{{baseUrl}}/api/Product" + } + }, + { + "name": "Update Product", + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"Updated Driver\",\n \"description\": \"Updated description\",\n \"quantityInStock\": 20\n}" + }, + "url": "{{baseUrl}}/api/Product/1" + } + }, + { + "name": "Delete Product", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/Product/1" + } + } + ] + }, + { + "name": "Categories", + "item": [ + { + "name": "Get All Categories (Paged + Sort)", + "request": { + "method": "GET", + "url": { + "raw": "{{baseUrl}}/api/Category?pageNumber=1&pageSize=10&sortBy=name", + "host": [ "{{baseUrl}}" ], + "path": [ "api", "Category" ], + "query": [ + { + "key": "pageNumber", + "value": "1" + }, + { + "key": "pageSize", + "value": "10" + }, + { + "key": "sortBy", + "value": "name" + } + ] + } + } + }, + { + "name": "Get Category by Id", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/Category/1" + } + }, + { + "name": "Create Category", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"Clubs\",\n \"description\": \"Drivers, Irons, Putters\"\n}" + }, + "url": "{{baseUrl}}/api/Category" + } + }, + { + "name": "Update Category", + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"Updated Category\",\n \"description\": \"Updated description\"\n}" + }, + "url": "{{baseUrl}}/api/Category/1" + } + }, + { + "name": "Delete Category", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/Category/1" + } + } + ] + }, + { + "name": "Sales", + "item": [ + { + "name": "Get All Sales (Paged)", + "request": { + "method": "GET", + "url": { + "raw": "{{baseUrl}}/api/Sales?pageNumber=1&pageSize=10", + "host": [ "{{baseUrl}}" ], + "path": [ "api", "Sales" ], + "query": [ + { + "key": "pageNumber", + "value": "1" + }, + { + "key": "pageSize", + "value": "10" + } + ] + } + } + }, + { + "name": "Get Sale by Id", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/Sales/1" + } + }, + { + "name": "Create Sale", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"customerName\": \"John Doe\",\n \"shippingAddress\": \"123 Main St\"\n}" + }, + "url": "{{baseUrl}}/api/Sales" + } + }, + { + "name": "Update Sale", + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"customerName\": \"Updated Name\",\n \"shippingAddress\": \"Updated Address\"\n}" + }, + "url": "{{baseUrl}}/api/Sales/1" + } + }, + { + "name": "Delete Sale", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/Sales/1" + } + } + ] + } + ], + "variable": [ + { + "key": "baseUrl", + "value": "https://localhost:8080", + "type": "string" + } + ] +} diff --git a/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Program.cs b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Program.cs new file mode 100644 index 00000000..797201b0 --- /dev/null +++ b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Program.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.OpenApi; +using Sills.GolfShop.eCommerceAPI.Data; +using Sills.GolfShop.eCommerceAPI.Services; + +var builder = WebApplication.CreateBuilder(args); + + +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddControllers(); +builder.Services.AddDbContext(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))); + +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); + + +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); + using (var scope = app.Services.CreateScope()) + { + var dbContext = scope.ServiceProvider.GetRequiredService(); + dbContext.Database.EnsureDeleted(); + dbContext.Database.EnsureCreated(); + } +} + +app.MapControllers(); + +app.Run(); diff --git a/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Properties/launchSettings.json b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Properties/launchSettings.json new file mode 100644 index 00000000..8cb9f9ee --- /dev/null +++ b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:8080", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7070;http://localhost:8080", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Services/ICategoryService.cs b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Services/ICategoryService.cs new file mode 100644 index 00000000..5d248bdc --- /dev/null +++ b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Services/ICategoryService.cs @@ -0,0 +1,80 @@ +using Microsoft.EntityFrameworkCore; +using Sills.GolfShop.eCommerceAPI.Data; +using Sills.GolfShop.eCommerceAPI.Models; +using Sills.GolfShop.eCommerceAPI.Services; + +namespace Sills.GolfShop.eCommerceAPI.Services; + + + public interface ICategoryService + { + IQueryable GetAllCategoriesQuery(); + Task GetCategoryByIdAsync(int id); + Task CreateCategoryAsync(Categories category); + Task UpdateCategoryAsync(int id, Categories category); + Task DeleteCategoryAsync(int id); + Task > GetPagedCategoriesAsync(int pageNumber, int pageSize); +} + +public class CategoryService : ICategoryService +{ + private readonly GolfShopDbContext _context; + + public CategoryService(GolfShopDbContext context) + { + _context = context; + } + + public IQueryable GetAllCategoriesQuery() + { + return _context.Categories.Where(c => c.DeletedAt == null); + } + + public async Task GetCategoryByIdAsync(int id) + { + return await _context.Categories + .Where(c => c.DeletedAt == null) + .FirstOrDefaultAsync(c => c.Id == id); + + } + + public async Task CreateCategoryAsync(Categories category) + { + _context.Categories.Add(category); + await _context.SaveChangesAsync(); + return category; + } + + public async Task UpdateCategoryAsync(int id, Categories category) + { + var existingCategory = await _context.Categories.FindAsync(id); + if (existingCategory == null) + { + return; + } + existingCategory.Name = category.Name; + existingCategory.Description = category.Description; + await _context.SaveChangesAsync(); + } + + public async Task DeleteCategoryAsync(int id) + { + var category = await _context.Categories.FindAsync(id); + if (category == null) + { + return; + } + category.DeletedAt = DateTime.UtcNow; + await _context.SaveChangesAsync(); + } + + public async Task> GetPagedCategoriesAsync(int pageNumber, int pageSize) + { + return await _context.Categories + .Skip((pageNumber - 1) * pageSize) + .Take(pageSize) + .ToListAsync(); + } +} + + diff --git a/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Services/IProductsService.cs b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Services/IProductsService.cs new file mode 100644 index 00000000..faf530ec --- /dev/null +++ b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Services/IProductsService.cs @@ -0,0 +1,83 @@ +using Microsoft.EntityFrameworkCore; +using Sills.GolfShop.eCommerceAPI.Data; +using Sills.GolfShop.eCommerceAPI.Models; + +namespace Sills.GolfShop.eCommerceAPI.Services; + + +public interface IProductsService +{ + Task> GetAllProductsAsync(); + Task GetProductByIdAsync(int id); + Task CreateProductAsync(Product product); + Task UpdateProductAsync(int id, Product product); + Task DeleteProductAsync(int id); + Task> GetPagedProductsAsync(int pageNumber, int pageSize); +} +public class ProductsService : IProductsService +{ + private readonly GolfShopDbContext _context; + + public ProductsService(GolfShopDbContext context) + { + _context = context; + } + + public async Task> GetAllProductsAsync() + { + return await _context.Products + .Where(p => p.DeletedAt == null) + .ToListAsync(); + } + + public async Task GetProductByIdAsync(int id) + { + return await _context.Products + .Where(p => p.DeletedAt == null) + .FirstOrDefaultAsync(p => p.Id == id); + } + + public async Task CreateProductAsync(Product product) + { + _context.Products.Add(product); + await _context.SaveChangesAsync(); + return product; + } + + public async Task UpdateProductAsync(int id, Product product) + { + var existingProduct = await _context.Products + .Where(p => p.DeletedAt == null) + .FirstOrDefaultAsync(p => p.Id == id); + if (existingProduct == null) + { + return; + } + existingProduct.Name = product.Name; + existingProduct.Description = product.Description; + existingProduct.Price = product.Price; + existingProduct.CategoryId = product.CategoryId; + await _context.SaveChangesAsync(); + } + + public async Task DeleteProductAsync(int id) + { + var product = await _context.Products.FindAsync(id); + if (product == null) + { + return; + } + product.DeletedAt = DateTime.UtcNow; + await _context.SaveChangesAsync(); + } + + public async Task> GetPagedProductsAsync(int pageNumber, int pageSize) + { + return await _context.Products + .Where(p => p.DeletedAt == null) + .OrderBy(p => p.Id) + .Skip((pageNumber - 1) * pageSize) + .Take(pageSize) + .ToListAsync(); + } +} \ No newline at end of file diff --git a/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Services/ISalesService.cs b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Services/ISalesService.cs new file mode 100644 index 00000000..60277644 --- /dev/null +++ b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Services/ISalesService.cs @@ -0,0 +1,100 @@ +using Microsoft.EntityFrameworkCore; +using Sills.GolfShop.eCommerceAPI.Data; +using Sills.GolfShop.eCommerceAPI.Models; + +namespace Sills.GolfShop.eCommerceAPI.Services; + +public interface ISalesService +{ + Task> GetAllSalesAsync(); + Task GetSaleByIdAsync(int id); + Task CreateSaleAsync(Sales sale); + Task DeleteSaleAsync(int id); + Task UpdateSaleAsync(int id, Sales sale); + Task> GetPagedSalesAsync(int pageNumber, int pageSize); +} +public class SalesService : ISalesService +{ + private readonly GolfShopDbContext _context; + + public SalesService(GolfShopDbContext context) + { + _context = context; + } + + + public async Task> GetAllSalesAsync() + { + return await _context.Sales.ToListAsync(); + } + + public async Task GetSaleByIdAsync(int id) + { + return await _context.Sales.FindAsync(id); + } + public async Task CreateSaleAsync(Sales sale) + { + _context.Sales.Add(sale); + await _context.SaveChangesAsync(); + return sale; + } + public async Task UpdateSaleAsync(int id, Sales sale) + { + var existingSale = await _context.Sales.FindAsync(id); + if (existingSale == null) + { + return; + } + existingSale.customerName = sale.customerName; + existingSale.shippingAddress = sale.shippingAddress; + await _context.SaveChangesAsync(); + } + public async Task DeleteSaleAsync(int id) + { + var sale = await _context.Sales.FindAsync(id); + if (sale == null) + { + return; + } + _context.Sales.Remove(sale); + await _context.SaveChangesAsync(); + } + public async Task AddProductToSaleAsync(int saleId, int productId) + { + var sale = await _context.Sales.FindAsync(saleId); + var product = await _context.Products.FindAsync(productId); + if (sale == null || product == null) + { + return; + } + var productSale = new ProductSales + { + Sale = sale, + Product = product + }; + _context.ProductSales.Add(productSale); + await _context.SaveChangesAsync(); + } + + public async Task RemoveProductFromSaleAsync(int saleId, int productId) + { + var productSale = await _context.ProductSales + .FirstOrDefaultAsync(ps => ps.SaleID == saleId && ps.ProductID == productId); + if (productSale == null) + { + return; + } + _context.ProductSales.Remove(productSale); + await _context.SaveChangesAsync(); + } + + public async Task> GetPagedSalesAsync(int pageNumber, int pageSize) + { + return await _context.Sales + .OrderBy(s => s.Id) + .Skip((pageNumber - 1) * pageSize) + .Take(pageSize) + .ToListAsync(); + } + +} diff --git a/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Sills.GolfShop.eCommerce.http b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Sills.GolfShop.eCommerce.http new file mode 100644 index 00000000..e69de29b diff --git a/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Sills.GolfShop.eCommerceAPI.csproj b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Sills.GolfShop.eCommerceAPI.csproj new file mode 100644 index 00000000..acf65e91 --- /dev/null +++ b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/Sills.GolfShop.eCommerceAPI.csproj @@ -0,0 +1,20 @@ + + + + net10.0 + enable + enable + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + diff --git a/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/appsettings.Development.json b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/appsettings.json b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/appsettings.json new file mode 100644 index 00000000..f8d66769 --- /dev/null +++ b/eCommerceApi.dsills735/Sills.GolfShop.eCommerce/appsettings.json @@ -0,0 +1,14 @@ +{ + "ConnectionStrings": { + "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=GolfShopDb;Trusted_Connection=True;MultipleActiveResultSets=true" + }, + + + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/eCommerceApi.dsills735/Sills.GolfShop.eCommerceFrontEnd/Controllers/ProductsController.cs b/eCommerceApi.dsills735/Sills.GolfShop.eCommerceFrontEnd/Controllers/ProductsController.cs new file mode 100644 index 00000000..6d710057 --- /dev/null +++ b/eCommerceApi.dsills735/Sills.GolfShop.eCommerceFrontEnd/Controllers/ProductsController.cs @@ -0,0 +1,27 @@ +using Sills.GolfShop.eCommerceFrontEnd.Models; +using Sills.GolfShop.eCommerceFrontEnd.Services; +using Spectre.Console; + +namespace Sills.GolfShop.eCommerceFrontEnd.Controllers; + +internal class ProductsController +{ + internal async Task AddProduct() + { + Console.Clear(); + + string ProductName = AnsiConsole.Ask("Enter the name of the product:"); + string ProductDescription = AnsiConsole.Ask("Enter the description of the product:"); + decimal ProductPrice = AnsiConsole.Ask("Enter the price of the product:"); + int ProductQuantity = AnsiConsole.Ask("Enter the quantity in stock for the product:"); + + Product product = new Product + { + Name = ProductName, + Description = ProductDescription, + Price = ProductPrice, + QuantityInStock = ProductQuantity + }; + await ProductService.AddProduct(product); + } +} diff --git a/eCommerceApi.dsills735/Sills.GolfShop.eCommerceFrontEnd/Controllers/SaleController.cs b/eCommerceApi.dsills735/Sills.GolfShop.eCommerceFrontEnd/Controllers/SaleController.cs new file mode 100644 index 00000000..baf7f7d3 --- /dev/null +++ b/eCommerceApi.dsills735/Sills.GolfShop.eCommerceFrontEnd/Controllers/SaleController.cs @@ -0,0 +1,5 @@ +namespace Sills.GolfShop.eCommerceFrontEnd.Controllers; + +internal class SaleController +{ +} diff --git a/eCommerceApi.dsills735/Sills.GolfShop.eCommerceFrontEnd/Helpers/Pagination.cs b/eCommerceApi.dsills735/Sills.GolfShop.eCommerceFrontEnd/Helpers/Pagination.cs new file mode 100644 index 00000000..ed978cb1 --- /dev/null +++ b/eCommerceApi.dsills735/Sills.GolfShop.eCommerceFrontEnd/Helpers/Pagination.cs @@ -0,0 +1,11 @@ + + +namespace Sills.GolfShop.eCommerceFrontEnd.Helpers; + +internal class Pagination +{ + internal static string GetPaginationQuery(int pageNumber, int pageSize) + { + return $"?pageNumber={pageNumber}&pageSize={pageSize}"; + } +} diff --git a/eCommerceApi.dsills735/Sills.GolfShop.eCommerceFrontEnd/Menus/AdministratorMenu.cs b/eCommerceApi.dsills735/Sills.GolfShop.eCommerceFrontEnd/Menus/AdministratorMenu.cs new file mode 100644 index 00000000..9eeaaeae --- /dev/null +++ b/eCommerceApi.dsills735/Sills.GolfShop.eCommerceFrontEnd/Menus/AdministratorMenu.cs @@ -0,0 +1,55 @@ +using Sills.GolfShop.eCommerceFrontEnd.Controllers; +using Spectre.Console; + +namespace Sills.GolfShop.eCommerceFrontEnd.Menus; + +internal class AdministratorMenu +{ + internal static async Task AdminMenuAsync() + { + bool running = true; + + while (running) + { + Console.Clear(); + + AnsiConsole.Write( + new FigletText("Admin Menu") + .LeftJustified() + .Color(Color.Green)); + + var choice = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("What would you like to do?") + .AddChoices(new[] { + "Add a product", + "Update a product", + "Delete a product", + "Add a category", + "Update a category", + "Delete a category", + "Exit" + })); + + switch (choice) + { + case "Add a product": + ProductsController productsController = new ProductsController(); + await productsController.AddProduct(); + break; + + case "Update a product": + //UpdateProductMenu.DisplayUpdateProductMenu(); + break; + + case "Delete a product": + //DeleteProductMenu.DisplayDeleteProductMenu(); + break; + + case "Exit": + running = false; + break; + } + } + } +} diff --git a/eCommerceApi.dsills735/Sills.GolfShop.eCommerceFrontEnd/Menus/MainMenu.cs b/eCommerceApi.dsills735/Sills.GolfShop.eCommerceFrontEnd/Menus/MainMenu.cs new file mode 100644 index 00000000..905c7edf --- /dev/null +++ b/eCommerceApi.dsills735/Sills.GolfShop.eCommerceFrontEnd/Menus/MainMenu.cs @@ -0,0 +1,69 @@ +using Sills.GolfShop.eCommerceFrontEnd.Services; +using Spectre.Console; + +namespace Sills.GolfShop.eCommerceFrontEnd.Menus; + +internal class MainMenu +{ + public static async Task MainDisplayAsync() + { + bool running = true; + + while (running) + { + //Console.Clear(); + + AnsiConsole.Write( + new FigletText("Sills Golf Shop") + .LeftJustified() + .Color(Color.Green)); + + var choice = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("What would you like to do?") + .AddChoices(new[] { + "Search for a product", + "Shop All Products", + "Shop from a category", + "View Cart", + "Checkout", + "View Order History", + "Administration Menu", + "Exit" + })); + + switch (choice) + { + case "Search for a product": + //SearchMenu.DisplaySearch(); + break; + + case "Shop All Products": + Console.Clear(); + ProductService productService = new ProductService(); + await productService.GetAllProductsAsync(); + break; + + case "Shop from a category": + //CategoryMenu.DisplayCategories(); + break; + case "View Cart": + //CartMenu.DisplayCart(); + break; + + case "Checkout": + break; + + case "View Order History": + break; + + case "Administration Menu": + break; + + case "Exit": + Environment.Exit(0); + break; + } + } + } +} \ No newline at end of file diff --git a/eCommerceApi.dsills735/Sills.GolfShop.eCommerceFrontEnd/Models/Category.cs b/eCommerceApi.dsills735/Sills.GolfShop.eCommerceFrontEnd/Models/Category.cs new file mode 100644 index 00000000..0f605f45 --- /dev/null +++ b/eCommerceApi.dsills735/Sills.GolfShop.eCommerceFrontEnd/Models/Category.cs @@ -0,0 +1,24 @@ +using Newtonsoft.Json; + +namespace Sills.GolfShop.eCommerceFrontEnd.Models; + +internal class Categories +{ + [JsonProperty("Category")] + public required List CategoriesList { get; set; } +} +public class Category +{ + [JsonProperty("Id")] + public int Id { get; set; } + [JsonProperty("Name")] + public string Name { get; set; } + [JsonProperty("Description")] + public string Description { get; set; } + [JsonProperty("DeletedAt")] + public DateTime? DeletedAt { get; set; } +} + //[JsonProperty("Products")] + + // public List Products { get; } = []; + diff --git a/eCommerceApi.dsills735/Sills.GolfShop.eCommerceFrontEnd/Models/Product.cs b/eCommerceApi.dsills735/Sills.GolfShop.eCommerceFrontEnd/Models/Product.cs new file mode 100644 index 00000000..22a117ab --- /dev/null +++ b/eCommerceApi.dsills735/Sills.GolfShop.eCommerceFrontEnd/Models/Product.cs @@ -0,0 +1,33 @@ +using Newtonsoft.Json; + +namespace Sills.GolfShop.eCommerceFrontEnd.Models; + +public class Products +{ + [JsonProperty("Product")] + public required List ProductsList { get; set; } +} + +public class Product +{ + [JsonProperty("Id")] + public int Id { get; set; } + [JsonProperty("Name")] + public string Name { get; set; } + [JsonProperty("Description")] + public string Description { get; set; } + [JsonProperty("Price")] + public decimal Price { get; set; } + [JsonProperty("QuantityInStock")] + public int QuantityInStock { get; set; } + [JsonProperty("DeletedAt")] + public DateTime? DeletedAt { get; set; } + [JsonProperty("ProductSales")] + public List ProductSales { get; } = []; + + [JsonProperty("CategoryId")] + public int CategoryId { get; internal set; } + + +} + diff --git a/eCommerceApi.dsills735/Sills.GolfShop.eCommerceFrontEnd/Models/ProductSales.cs b/eCommerceApi.dsills735/Sills.GolfShop.eCommerceFrontEnd/Models/ProductSales.cs new file mode 100644 index 00000000..7af2e97f --- /dev/null +++ b/eCommerceApi.dsills735/Sills.GolfShop.eCommerceFrontEnd/Models/ProductSales.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace Sills.GolfShop.eCommerceFrontEnd.Models; + +public class ProductSales +{ + [JsonProperty("ProductSales")] + public required List ProductSalesList { get; set; } +} + +public class ProductSale +{ + [JsonProperty("Id")] + public int Id { get; set; } + [JsonProperty("ProductId")] + public int ProductId { get; set; } + [JsonProperty("SaleId")] + public int SaleId { get; set; } +} diff --git a/eCommerceApi.dsills735/Sills.GolfShop.eCommerceFrontEnd/Models/Sales.cs b/eCommerceApi.dsills735/Sills.GolfShop.eCommerceFrontEnd/Models/Sales.cs new file mode 100644 index 00000000..83d1167c --- /dev/null +++ b/eCommerceApi.dsills735/Sills.GolfShop.eCommerceFrontEnd/Models/Sales.cs @@ -0,0 +1,20 @@ +using Newtonsoft.Json; + +namespace Sills.GolfShop.eCommerceFrontEnd.Models; + +internal class Sales +{ + [JsonProperty("Sales")] + public required List SalesList { get; set; } +} +internal class Sale +{ + [JsonProperty("Id")] + public int Id { get; set; } + [JsonProperty("CustomerName")] + public string CustomerName { get; set; } + [JsonProperty("ShippingAddress")] + public string ShippingAddress { get; set; } = string.Empty; + [JsonProperty("ProductSales")] + public List ProductSales { get; } = []; +} diff --git a/eCommerceApi.dsills735/Sills.GolfShop.eCommerceFrontEnd/Program.cs b/eCommerceApi.dsills735/Sills.GolfShop.eCommerceFrontEnd/Program.cs new file mode 100644 index 00000000..fd129fc2 --- /dev/null +++ b/eCommerceApi.dsills735/Sills.GolfShop.eCommerceFrontEnd/Program.cs @@ -0,0 +1,10 @@ +namespace Sills.GolfShop.eCommerceFrontEnd; + +public class Program +{ + public static async Task Main(string[] args) + { + await Menus.MainMenu.MainDisplayAsync(); + } + +} \ No newline at end of file diff --git a/eCommerceApi.dsills735/Sills.GolfShop.eCommerceFrontEnd/Services/ProductService.cs b/eCommerceApi.dsills735/Sills.GolfShop.eCommerceFrontEnd/Services/ProductService.cs new file mode 100644 index 00000000..0a72aac0 --- /dev/null +++ b/eCommerceApi.dsills735/Sills.GolfShop.eCommerceFrontEnd/Services/ProductService.cs @@ -0,0 +1,68 @@ + +using Newtonsoft.Json; +using Sills.GolfShop.eCommerceFrontEnd.Models; +using Sills.GolfShop.eCommerceFrontEnd.Visualizations; + + +namespace Sills.GolfShop.eCommerceFrontEnd.Services; + +internal class ProductService +{ + private static readonly HttpClient client = new HttpClient(); + + static ProductService() + { + client.BaseAddress = new Uri("http://localhost:8080/"); + client.Timeout = TimeSpan.FromSeconds(30); + } + + + + internal async Task GetAllProductsAsync() + { + //TODO finish once admin area is done. + try + { + var response = await client.GetStringAsync("api/Product?PageNumber=1&PageSize=20"); + + if (response == null) + { + Console.WriteLine("No products found."); + return; + } + var deserializedProducts = JsonConvert.DeserializeObject>(response); + + + if (deserializedProducts != null && deserializedProducts.Count > 0) + { + ProductVisualizations.DisplayProductsTable(deserializedProducts); + } + else + { + Console.WriteLine("No products found."); + + } + } + + catch (HttpRequestException ex) + { + Console.WriteLine($"Error fetching products: {ex.Message}"); + return; + + } + catch(JsonException ex) + { + Console.WriteLine($"Error parsing product data: {ex.Message}"); + return; + } + catch (Exception ex) + { + Console.WriteLine($"Unexpected error: {ex.Message}"); + return; + } + } + internal static async Task AddProduct(Product product) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/eCommerceApi.dsills735/Sills.GolfShop.eCommerceFrontEnd/Sills.GolfShop.eCommerceFrontEnd.csproj b/eCommerceApi.dsills735/Sills.GolfShop.eCommerceFrontEnd/Sills.GolfShop.eCommerceFrontEnd.csproj new file mode 100644 index 00000000..e79be62b --- /dev/null +++ b/eCommerceApi.dsills735/Sills.GolfShop.eCommerceFrontEnd/Sills.GolfShop.eCommerceFrontEnd.csproj @@ -0,0 +1,15 @@ + + + + Exe + net10.0 + enable + enable + + + + + + + + diff --git a/eCommerceApi.dsills735/Sills.GolfShop.eCommerceFrontEnd/Visualizations/ProductVisualizations.cs b/eCommerceApi.dsills735/Sills.GolfShop.eCommerceFrontEnd/Visualizations/ProductVisualizations.cs new file mode 100644 index 00000000..18b11a09 --- /dev/null +++ b/eCommerceApi.dsills735/Sills.GolfShop.eCommerceFrontEnd/Visualizations/ProductVisualizations.cs @@ -0,0 +1,29 @@ +using Sills.GolfShop.eCommerceFrontEnd.Models; +using Spectre.Console; +namespace Sills.GolfShop.eCommerceFrontEnd.Visualizations; + +internal class ProductVisualizations +{ + internal static void DisplayProductsTable(List products, bool stay = true) + { + var table = new Table() + .RoundedBorder() + .BorderColor(Color.Green) + .AddColumn("Product Name") + .AddColumn("Description") + .AddColumn("Price"); + + foreach (var product in products) + { + table.AddRow(product.Name, product.Description, product.Price.ToString("C")); + } + AnsiConsole.Write(table); + + if (stay) + { + Console.WriteLine("Press any key to return to the menu"); + Console.ReadKey(); + Console.Clear(); + } + } +} \ No newline at end of file