diff --git a/Ecommerce.API.davetn657/Controllers/CategoriesController.cs b/Ecommerce.API.davetn657/Controllers/CategoriesController.cs new file mode 100644 index 00000000..1aa15da1 --- /dev/null +++ b/Ecommerce.API.davetn657/Controllers/CategoriesController.cs @@ -0,0 +1,48 @@ +using Ecommerce.API.davetn657.Data; +using Ecommerce.API.davetn657.Data.Dtos; +using Ecommerce.API.davetn657.Services; +using Ecommerce.API.davetn657.Models; +using Microsoft.AspNetCore.Mvc; + +namespace Ecommerce.API.davetn657.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class CategoriesController : ControllerBase +{ + private readonly ICategoryService _categoryService; + + public CategoriesController(ICategoryService categoryServices) + { + _categoryService = categoryServices; + } + + [HttpGet] + public ActionResult> AllCategories() + { + return Ok(_categoryService.AllCategories(new PaginationParams())); + } + [HttpGet("id/{id}")] + public ActionResult CategoryById(int id) + { + var result = _categoryService.CategoryById(id); + + if (result == null) return NotFound(); + + return Ok(result); + } + [HttpPost] + public ActionResult CreateCategory(CategoriesDto category) + { + return Ok(_categoryService.CreateCategory(category)); + } + [HttpDelete("{id}")] + public ActionResult DeleteCategory(int id) + { + var result = _categoryService.DeleteCategory(id); + + if (result == null) return NotFound(); + + return Ok(result); + } +} \ No newline at end of file diff --git a/Ecommerce.API.davetn657/Controllers/ProductsController.cs b/Ecommerce.API.davetn657/Controllers/ProductsController.cs new file mode 100644 index 00000000..c9458d20 --- /dev/null +++ b/Ecommerce.API.davetn657/Controllers/ProductsController.cs @@ -0,0 +1,41 @@ +using Ecommerce.API.davetn657.Data; +using Ecommerce.API.davetn657.Data.Dtos; +using Ecommerce.API.davetn657.Services; +using Ecommerce.API.davetn657.Models; +using Microsoft.AspNetCore.Mvc; + + +namespace Ecommerce.API.davetn657.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class ProductsController : ControllerBase +{ + private readonly IProductService _productService; + + public ProductsController(IProductService productService) + { + _productService = productService; + } + [HttpGet] + public ActionResult> AllProducts() + { + return Ok(_productService.AllProducts(new PaginationParams())); + } + [HttpGet("category/{category}")] + public ActionResult> ProductsByCategory(string category) + { + return Ok(_productService.ProductsByCategory( category, new PaginationParams())); + } + [HttpGet("id/{id}")] + public ActionResult ProductById(int id) + { + return Ok(_productService.ProductById(id)); + } + [HttpPost] + public ActionResult CreateProduct(ProductsDto product) + { + return Ok(_productService.CreateProduct(product)); + } + +} \ No newline at end of file diff --git a/Ecommerce.API.davetn657/Controllers/SalesController.cs b/Ecommerce.API.davetn657/Controllers/SalesController.cs new file mode 100644 index 00000000..dd607af4 --- /dev/null +++ b/Ecommerce.API.davetn657/Controllers/SalesController.cs @@ -0,0 +1,35 @@ +using Ecommerce.API.davetn657.Data; +using Ecommerce.API.davetn657.Data.Dtos; +using Ecommerce.API.davetn657.Models; +using Ecommerce.API.davetn657.Services; +using Microsoft.AspNetCore.Mvc; + +namespace Ecommerce.API.davetn657.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class SalesController : ControllerBase +{ + private readonly ISaleService _saleService; + + public SalesController(ISaleService saleService) + { + _saleService = saleService; + } + + [HttpGet] + public ActionResult> AllSales() + { + return Ok(_saleService.AllSales(new PaginationParams())); + } + [HttpGet("id/{id}")] + public ActionResult SaleById(int id) + { + return Ok(_saleService.SaleById(id)); + } + [HttpPost] + public ActionResult CreateSale(CreateSalesDto sale) + { + return Ok(_saleService.CreateSale(sale)); + } +} \ No newline at end of file diff --git a/Ecommerce.API.davetn657/Data/Categories.cs b/Ecommerce.API.davetn657/Data/Categories.cs new file mode 100644 index 00000000..4164f712 --- /dev/null +++ b/Ecommerce.API.davetn657/Data/Categories.cs @@ -0,0 +1,8 @@ +namespace Ecommerce.API.davetn657.Data; + +public class Category +{ + public int Id { get; set; } + public string Name { get; set; } = string.Empty; + public DateTime CreateDate { get; set; } +} \ No newline at end of file diff --git a/Ecommerce.API.davetn657/Data/DatabaseContext.cs b/Ecommerce.API.davetn657/Data/DatabaseContext.cs new file mode 100644 index 00000000..2195bef1 --- /dev/null +++ b/Ecommerce.API.davetn657/Data/DatabaseContext.cs @@ -0,0 +1,15 @@ +using Microsoft.EntityFrameworkCore; + +namespace Ecommerce.API.davetn657.Data; + +public class DatabaseContext : DbContext +{ + public DatabaseContext(DbContextOptions options) : base(options) + { + + } + + public DbSet Categories { get; set; } + public DbSet Products { get; set; } + public DbSet Sales { get; set; } +} \ No newline at end of file diff --git a/Ecommerce.API.davetn657/Data/Dtos/CategoriesDto.cs b/Ecommerce.API.davetn657/Data/Dtos/CategoriesDto.cs new file mode 100644 index 00000000..41d9101a --- /dev/null +++ b/Ecommerce.API.davetn657/Data/Dtos/CategoriesDto.cs @@ -0,0 +1,7 @@ +namespace Ecommerce.API.davetn657.Data.Dtos; + +public class CategoriesDto +{ + public int Id { get; set; } + public string Name { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/Ecommerce.API.davetn657/Data/Dtos/ProductsDto.cs b/Ecommerce.API.davetn657/Data/Dtos/ProductsDto.cs new file mode 100644 index 00000000..61a57f1a --- /dev/null +++ b/Ecommerce.API.davetn657/Data/Dtos/ProductsDto.cs @@ -0,0 +1,18 @@ +namespace Ecommerce.API.davetn657.Data.Dtos; + +public class ProductsDto +{ + public int Id { get; set; } + public string Name { get; set; } = string.Empty; + public decimal Price { get; set; } + public string Category { get; set; } = string.Empty; + public List Sales { get; set; } = []; +} + +public class ProductsSalesDto +{ + public int Id { get; set; } + public string Name { get; set; } = string.Empty; + public decimal Price { get; set; } + public string Category { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/Ecommerce.API.davetn657/Data/Dtos/SalesDto.cs b/Ecommerce.API.davetn657/Data/Dtos/SalesDto.cs new file mode 100644 index 00000000..cd22505c --- /dev/null +++ b/Ecommerce.API.davetn657/Data/Dtos/SalesDto.cs @@ -0,0 +1,20 @@ +namespace Ecommerce.API.davetn657.Data.Dtos; + +public class SalesDto +{ + public int Id { get; set; } + public List ProductIds { get; set; } = []; + public decimal Total { get; set; } +} + +public class CreateSalesDto +{ + public List ProductIds { get; set; } = []; +} + +public class SalesResponseDto +{ + public int Id { get; set; } + public decimal Total { get; set; } + public List Products { get; set; } = []; +} \ No newline at end of file diff --git a/Ecommerce.API.davetn657/Data/Migrations/20260320171417_InitialCreate.Designer.cs b/Ecommerce.API.davetn657/Data/Migrations/20260320171417_InitialCreate.Designer.cs new file mode 100644 index 00000000..fb325c28 --- /dev/null +++ b/Ecommerce.API.davetn657/Data/Migrations/20260320171417_InitialCreate.Designer.cs @@ -0,0 +1,109 @@ +// +using System; +using Ecommerce.API.davetn657.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Ecommerce.API.davetn657.Data.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20260320171417_InitialCreate")] + partial class InitialCreate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.5"); + + modelBuilder.Entity("Ecommerce.API.davetn657.Data.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreateDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("Ecommerce.API.davetn657.Data.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Category") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Price") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Products"); + }); + + modelBuilder.Entity("Ecommerce.API.davetn657.Data.Sale", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Total") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Sales"); + }); + + modelBuilder.Entity("ProductSale", b => + { + b.Property("ProductsId") + .HasColumnType("INTEGER"); + + b.Property("SalesId") + .HasColumnType("INTEGER"); + + b.HasKey("ProductsId", "SalesId"); + + b.HasIndex("SalesId"); + + b.ToTable("ProductSale"); + }); + + modelBuilder.Entity("ProductSale", b => + { + b.HasOne("Ecommerce.API.davetn657.Data.Product", null) + .WithMany() + .HasForeignKey("ProductsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Ecommerce.API.davetn657.Data.Sale", null) + .WithMany() + .HasForeignKey("SalesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Ecommerce.API.davetn657/Data/Migrations/20260320171417_InitialCreate.cs b/Ecommerce.API.davetn657/Data/Migrations/20260320171417_InitialCreate.cs new file mode 100644 index 00000000..0475ece9 --- /dev/null +++ b/Ecommerce.API.davetn657/Data/Migrations/20260320171417_InitialCreate.cs @@ -0,0 +1,102 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Ecommerce.API.davetn657.Data.Migrations +{ + /// + public partial class InitialCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Categories", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(type: "TEXT", nullable: false), + CreateDate = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Categories", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Products", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(type: "TEXT", nullable: false), + Price = table.Column(type: "TEXT", nullable: false), + Category = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Products", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Sales", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Total = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Sales", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ProductSale", + columns: table => new + { + ProductsId = table.Column(type: "INTEGER", nullable: false), + SalesId = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ProductSale", x => new { x.ProductsId, x.SalesId }); + table.ForeignKey( + name: "FK_ProductSale_Products_ProductsId", + column: x => x.ProductsId, + principalTable: "Products", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ProductSale_Sales_SalesId", + column: x => x.SalesId, + principalTable: "Sales", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_ProductSale_SalesId", + table: "ProductSale", + column: "SalesId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Categories"); + + migrationBuilder.DropTable( + name: "ProductSale"); + + migrationBuilder.DropTable( + name: "Products"); + + migrationBuilder.DropTable( + name: "Sales"); + } + } +} diff --git a/Ecommerce.API.davetn657/Data/Migrations/DatabaseContextModelSnapshot.cs b/Ecommerce.API.davetn657/Data/Migrations/DatabaseContextModelSnapshot.cs new file mode 100644 index 00000000..bd2b543b --- /dev/null +++ b/Ecommerce.API.davetn657/Data/Migrations/DatabaseContextModelSnapshot.cs @@ -0,0 +1,106 @@ +// +using System; +using Ecommerce.API.davetn657.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Ecommerce.API.davetn657.Data.Migrations +{ + [DbContext(typeof(DatabaseContext))] + partial class DatabaseContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.5"); + + modelBuilder.Entity("Ecommerce.API.davetn657.Data.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreateDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("Ecommerce.API.davetn657.Data.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Category") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Price") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Products"); + }); + + modelBuilder.Entity("Ecommerce.API.davetn657.Data.Sale", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Total") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Sales"); + }); + + modelBuilder.Entity("ProductSale", b => + { + b.Property("ProductsId") + .HasColumnType("INTEGER"); + + b.Property("SalesId") + .HasColumnType("INTEGER"); + + b.HasKey("ProductsId", "SalesId"); + + b.HasIndex("SalesId"); + + b.ToTable("ProductSale"); + }); + + modelBuilder.Entity("ProductSale", b => + { + b.HasOne("Ecommerce.API.davetn657.Data.Product", null) + .WithMany() + .HasForeignKey("ProductsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Ecommerce.API.davetn657.Data.Sale", null) + .WithMany() + .HasForeignKey("SalesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Ecommerce.API.davetn657/Data/Products.cs b/Ecommerce.API.davetn657/Data/Products.cs new file mode 100644 index 00000000..f469e2d6 --- /dev/null +++ b/Ecommerce.API.davetn657/Data/Products.cs @@ -0,0 +1,9 @@ +namespace Ecommerce.API.davetn657.Data; +public class Product +{ + public int Id { get; set; } + public string Name { get; set; } = string.Empty; + public decimal Price { get; set; } + public List Sales { get; set; } = []; + public string Category { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/Ecommerce.API.davetn657/Data/Sales.cs b/Ecommerce.API.davetn657/Data/Sales.cs new file mode 100644 index 00000000..03e27daf --- /dev/null +++ b/Ecommerce.API.davetn657/Data/Sales.cs @@ -0,0 +1,8 @@ +namespace Ecommerce.API.davetn657.Data; + +public class Sale +{ + public int Id { get; set; } + public decimal Total { get; set; } + public List Products { get; set; } = []; +} \ No newline at end of file diff --git a/Ecommerce.API.davetn657/Ecommerce.API.davetn657.csproj b/Ecommerce.API.davetn657/Ecommerce.API.davetn657.csproj new file mode 100644 index 00000000..f4befeb6 --- /dev/null +++ b/Ecommerce.API.davetn657/Ecommerce.API.davetn657.csproj @@ -0,0 +1,23 @@ + + + + net10.0 + enable + enable + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/Ecommerce.API.davetn657/Ecommerce.API.davetn657.http b/Ecommerce.API.davetn657/Ecommerce.API.davetn657.http new file mode 100644 index 00000000..da8b7c7a --- /dev/null +++ b/Ecommerce.API.davetn657/Ecommerce.API.davetn657.http @@ -0,0 +1,6 @@ +@Ecommerce.API.davetn657_HostAddress = http://localhost:5096 + +GET {{Ecommerce.API.davetn657_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/Ecommerce.API.davetn657/Ecommerce.API.davetn657.slnx b/Ecommerce.API.davetn657/Ecommerce.API.davetn657.slnx new file mode 100644 index 00000000..4b7c664e --- /dev/null +++ b/Ecommerce.API.davetn657/Ecommerce.API.davetn657.slnx @@ -0,0 +1,3 @@ + + + diff --git a/Ecommerce.API.davetn657/Ecommerce.db b/Ecommerce.API.davetn657/Ecommerce.db new file mode 100644 index 00000000..47ec0ff0 Binary files /dev/null and b/Ecommerce.API.davetn657/Ecommerce.db differ diff --git a/Ecommerce.API.davetn657/Ecommerce.db-shm b/Ecommerce.API.davetn657/Ecommerce.db-shm new file mode 100644 index 00000000..46bbeb21 Binary files /dev/null and b/Ecommerce.API.davetn657/Ecommerce.db-shm differ diff --git a/Ecommerce.API.davetn657/Ecommerce.db-wal b/Ecommerce.API.davetn657/Ecommerce.db-wal new file mode 100644 index 00000000..a7249d31 Binary files /dev/null and b/Ecommerce.API.davetn657/Ecommerce.db-wal differ diff --git a/Ecommerce.API.davetn657/Models/PagedResponse.cs b/Ecommerce.API.davetn657/Models/PagedResponse.cs new file mode 100644 index 00000000..dfbaaaec --- /dev/null +++ b/Ecommerce.API.davetn657/Models/PagedResponse.cs @@ -0,0 +1,17 @@ +namespace Ecommerce.API.davetn657.Models; + +public class PagedResponse +{ + public List Data { get; set; } = []; + public int PageNumber { get; set; } + public int PageSize { get; set; } + public int TotalRecords { get; set; } + + public PagedResponse(List data, int pageNumber, int pageSize, int totalRecords) + { + Data = data; + PageNumber = pageNumber; + PageSize = pageSize; + TotalRecords = totalRecords; + } +} \ No newline at end of file diff --git a/Ecommerce.API.davetn657/Models/PaginationParams.cs b/Ecommerce.API.davetn657/Models/PaginationParams.cs new file mode 100644 index 00000000..d8a2989d --- /dev/null +++ b/Ecommerce.API.davetn657/Models/PaginationParams.cs @@ -0,0 +1,14 @@ +namespace Ecommerce.API.davetn657.Models; + +public class PaginationParams +{ + private 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; + } +} \ No newline at end of file diff --git a/Ecommerce.API.davetn657/Program.cs b/Ecommerce.API.davetn657/Program.cs new file mode 100644 index 00000000..bfd9860d --- /dev/null +++ b/Ecommerce.API.davetn657/Program.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore; +using Ecommerce.API.davetn657.Data; +using Ecommerce.API.davetn657.Services; + +namespace Ecommerce.API.davetn657; + +public class Program +{ + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + + builder.Services.AddAuthorization(); + builder.Services.AddOpenApi(); + builder.Services.AddControllers(); + + builder.Services.AddDbContext(opt => opt.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection"))); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + + var app = builder.Build(); + + if (app.Environment.IsDevelopment()) + { + app.MapOpenApi(); + } + + app.MapControllers(); + + app.Run(); + } +} \ No newline at end of file diff --git a/Ecommerce.API.davetn657/Properties/launchSettings.json b/Ecommerce.API.davetn657/Properties/launchSettings.json new file mode 100644 index 00000000..5336d038 --- /dev/null +++ b/Ecommerce.API.davetn657/Properties/launchSettings.json @@ -0,0 +1,22 @@ +{ + "profiles": { + "http": { + "commandName": "Project", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "http://localhost:5096" + }, + "https": { + "commandName": "Project", + "workingDirectory": "C:\\Users\\davei\\Documents\\.Coding\\.C#Academy\\.Backup\\Ecommerce.API.davetn657\\Ecommerce.API.davetn657", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "https://localhost:7265;http://localhost:5096" + } + }, + "$schema": "https://json.schemastore.org/launchsettings.json" +} \ No newline at end of file diff --git a/Ecommerce.API.davetn657/Services/CategoryService.cs b/Ecommerce.API.davetn657/Services/CategoryService.cs new file mode 100644 index 00000000..d94ab70f --- /dev/null +++ b/Ecommerce.API.davetn657/Services/CategoryService.cs @@ -0,0 +1,82 @@ +using Ecommerce.API.davetn657.Data; +using Ecommerce.API.davetn657.Data.Dtos; +using Ecommerce.API.davetn657.Models; + +namespace Ecommerce.API.davetn657.Services; + +public interface ICategoryService +{ + public PagedResponse AllCategories(PaginationParams pagination); + public CategoriesDto? CategoryById(int id); + public CategoriesDto CreateCategory(CategoriesDto category); + public string? DeleteCategory(int id); +} + +public class CategoryService : ICategoryService +{ + private readonly DatabaseContext _dbContext; + + public CategoryService(DatabaseContext dbContext) + { + _dbContext = dbContext; + } + + public PagedResponse AllCategories(PaginationParams pagination) + { + var query = _dbContext.Categories.AsQueryable(); + var totalRecords = query.Count(); + var categories = query + .Skip((pagination.PageNumber - 1) * pagination.PageSize) + .Take(pagination.PageSize) + .Select(c => new CategoriesDto + { + Id = c.Id, + Name = c.Name + }).ToList() + .ToList(); + + var pagedResponse = new PagedResponse(categories, pagination.PageNumber, pagination.PageSize, totalRecords); + + return pagedResponse; + } + public CategoriesDto? CategoryById(int id) + { + var savedCategory = _dbContext.Categories + .Where(c => c.Id == id) + .Select(c => new CategoriesDto + { + Id = c.Id, + Name = c.Name + }).FirstOrDefault(); + + if (savedCategory == null) return null; + return savedCategory; + } + public CategoriesDto CreateCategory(CategoriesDto category) + { + var savedCategory = new Category + { + Id = category.Id, + Name = category.Name + }; + + _dbContext.Categories.Add(savedCategory); + _dbContext.SaveChanges(); + + return new CategoriesDto + { + Id = savedCategory.Id, + Name = savedCategory.Name + }; + } + public string? DeleteCategory(int id) + { + var savedCategory = _dbContext.Categories.Find(id); + if (savedCategory == null) return null; + + _dbContext.Categories.Remove(savedCategory); + _dbContext.SaveChanges(); + + return $"Successfully deleted category with id {savedCategory.Id}"; + } +} \ No newline at end of file diff --git a/Ecommerce.API.davetn657/Services/ProductService.cs b/Ecommerce.API.davetn657/Services/ProductService.cs new file mode 100644 index 00000000..ce6e0c02 --- /dev/null +++ b/Ecommerce.API.davetn657/Services/ProductService.cs @@ -0,0 +1,119 @@ +using Ecommerce.API.davetn657.Data; +using Ecommerce.API.davetn657.Data.Dtos; +using Ecommerce.API.davetn657.Models; +using Microsoft.EntityFrameworkCore; + +namespace Ecommerce.API.davetn657.Services; + +public interface IProductService +{ + /* + CreateProduct + ViewAllProducts + ViewProductById + ViewProductByCategory + */ + + public PagedResponse AllProducts(PaginationParams pagination); + public PagedResponse ProductsByCategory(string category, PaginationParams pagination); + public ProductsDto? ProductById(int id); + public ProductsDto CreateProduct(ProductsDto product); +} + +public class ProductService : IProductService +{ + private readonly DatabaseContext _dbContext; + public ProductService(DatabaseContext dbContext) + { + _dbContext = dbContext; + } + + public PagedResponse AllProducts(PaginationParams pagination) + { + var query = _dbContext.Products.AsQueryable(); + var totalRecords = query.Count(); + var products = query + .Skip((pagination.PageNumber - 1) * pagination.PageSize) + .Take(pagination.PageSize) + .Select(p => new ProductsDto + { + Id = p.Id, + Name = p.Name, + Price = p.Price, + Category = p.Category, + Sales = p.Sales.Select(s => new SalesDto + { + Id = s.Id, + Total = s.Total, + ProductIds = s.Products.Select(p => p.Id).ToList() + }).ToList() + }).ToList(); + + var pagedResponse = new PagedResponse(products, pagination.PageNumber, pagination.PageSize, totalRecords); + + return pagedResponse; + } + public PagedResponse ProductsByCategory(string category, PaginationParams pagination) + { + var query = _dbContext.Products.Where(p => p.Category.ToLower() == category.ToLower()); + var totalRecords = query.Count(); + var products = query + .Skip((pagination.PageNumber - 1) * pagination.PageSize) + .Take(pagination.PageSize) + .Select(p => new ProductsDto + { + Id = p.Id, + Name = p.Name, + Price = p.Price, + Category = p.Category, + Sales = p.Sales.Select(s => new SalesDto + { + Id = s.Id, + Total = s.Total, + ProductIds = s.Products.Select(p => p.Id).ToList() + }).ToList() + }).ToList(); + + var pagedResponse = new PagedResponse(products, pagination.PageNumber, pagination.PageSize, totalRecords); + + return pagedResponse; + } + public ProductsDto? ProductById(int id) + { + return _dbContext.Products + .Where(p => p.Id == id) + .Select(p => new ProductsDto + { + Id = p.Id, + Name = p.Name, + Price = p.Price, + Category = p.Category, + Sales = p.Sales.Select(s => new SalesDto + { + Id = s.Id, + Total = s.Total, + ProductIds = s.Products.Select(p => p.Id).ToList() + }).ToList() + }).FirstOrDefault(); + } + public ProductsDto CreateProduct(ProductsDto product) + { + var savedProduct = new Product + { + Id = product.Id, + Name = product.Name, + Price = product.Price, + Category = product.Category + }; + + _dbContext.Products.Add(savedProduct); + _dbContext.SaveChanges(); + return new ProductsDto + { + Id = product.Id, + Name = product.Name, + Price = product.Price, + Category = product.Category + }; + } +} \ No newline at end of file diff --git a/Ecommerce.API.davetn657/Services/SaleService.cs b/Ecommerce.API.davetn657/Services/SaleService.cs new file mode 100644 index 00000000..9e0f276c --- /dev/null +++ b/Ecommerce.API.davetn657/Services/SaleService.cs @@ -0,0 +1,102 @@ +using Ecommerce.API.davetn657.Data; +using Ecommerce.API.davetn657.Data.Dtos; +using Ecommerce.API.davetn657.Models; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace Ecommerce.API.davetn657.Services; + +public interface ISaleService +{ + /* + * View All Sales + * View Sale By ID + * Create New Sale + */ + public PagedResponse AllSales(PaginationParams pagination); + public SalesResponseDto? SaleById(int id); + public SalesResponseDto CreateSale(CreateSalesDto sale); +} + +public class SaleService : ISaleService +{ + private readonly DatabaseContext _dbContext; + public SaleService(DatabaseContext dbContext) + { + _dbContext = dbContext; + } + + public PagedResponse AllSales([FromQuery] PaginationParams pagination) + { + var query = _dbContext.Sales.AsQueryable(); + var totalRecords = query.Count(); + var sales = query + .Skip((pagination.PageNumber - 1) * pagination.PageSize) + .Take(pagination.PageSize) + .Select(s => new SalesResponseDto + { + Id = s.Id, + Total = s.Total, + Products = s.Products.Select(p => new ProductsSalesDto + { + Name = p.Name, + Id = p.Id, + Price = p.Price, + Category = p.Category + }).ToList() + + }).ToList(); + + var pagedResponse = new PagedResponse(sales, pagination.PageNumber, pagination.PageSize, totalRecords); + + return pagedResponse; + } + public SalesResponseDto? SaleById(int id) + { + return _dbContext.Sales + .Where(s => s.Id == id) + .Select(s => new SalesResponseDto + { + Id = s.Id, + Total = s.Total, + Products = s.Products.Select(p => new ProductsSalesDto + { + Id = p.Id, + Price = p.Price, + Category = p.Category + }).ToList() + }).FirstOrDefault(); + } + public SalesResponseDto CreateSale(CreateSalesDto sale) + { + var products = _dbContext.Products + .Where(s => sale.ProductIds.Contains(s.Id)) + .ToList(); + + if(products.Count != sale.ProductIds.Count) + { + throw new Exception("Could not find one or more products"); + } + + var savedSale = new Sale + { + Products = products, + Total = products.Sum(s => s.Price) + }; + + _dbContext.Sales.Add(savedSale); + _dbContext.SaveChanges(); + + return new SalesResponseDto + { + Id = savedSale.Id, + Total = savedSale.Total, + Products = savedSale.Products.Select(p => new ProductsSalesDto + { + Id = p.Id, + Price = p.Price, + Category = p.Category + }).ToList() + }; + } +} \ No newline at end of file diff --git a/Ecommerce.API.davetn657/appsettings.Development.json b/Ecommerce.API.davetn657/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/Ecommerce.API.davetn657/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Ecommerce.API.davetn657/appsettings.json b/Ecommerce.API.davetn657/appsettings.json new file mode 100644 index 00000000..dc74f6ca --- /dev/null +++ b/Ecommerce.API.davetn657/appsettings.json @@ -0,0 +1,12 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "ConnectionStrings": { + "DefaultConnection": "Data Source=Ecommerce.db" + }, + "AllowedHosts": "*" +} diff --git a/EcommerceAPI.postman_collection.json b/EcommerceAPI.postman_collection.json new file mode 100644 index 00000000..d8e6c30d --- /dev/null +++ b/EcommerceAPI.postman_collection.json @@ -0,0 +1,320 @@ +{ + "info": { + "_postman_id": "0c411631-dbc1-441e-95d7-36174ff7de70", + "name": "EcommerceAPI", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "53190867", + "_collection_link": "https://go.postman.co/collection/53190867-0c411631-dbc1-441e-95d7-36174ff7de70?source=collection_link" + }, + "item": [ + { + "name": "Categories Post", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"Name\":\"Socks\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "https://localhost:7265/api/Categories", + "protocol": "https", + "host": [ + "localhost" + ], + "port": "7265", + "path": [ + "api", + "Categories" + ] + } + }, + "response": [] + }, + { + "name": "Get All Categories", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"Price\": 18.99,\r\n \"Category\": \"Pants\"\r\n}" + }, + "url": { + "raw": "https://localhost:7265/api/Categories", + "protocol": "https", + "host": [ + "localhost" + ], + "port": "7265", + "path": [ + "api", + "Categories" + ] + } + }, + "response": [] + }, + { + "name": "Get Category By Id", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"Price\": 18.99,\r\n \"Category\": \"Pants\"\r\n}" + }, + "url": { + "raw": "https://localhost:7265/api/Categories/id/1", + "protocol": "https", + "host": [ + "localhost" + ], + "port": "7265", + "path": [ + "api", + "Categories", + "id", + "1" + ] + } + }, + "response": [] + }, + { + "name": "Delete Category", + "request": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"Price\": 18.99,\r\n \"Category\": \"Pants\"\r\n}" + }, + "url": { + "raw": "https://localhost:7265/api/Categories/1", + "protocol": "https", + "host": [ + "localhost" + ], + "port": "7265", + "path": [ + "api", + "Categories", + "1" + ] + } + }, + "response": [] + }, + { + "name": "Products Post", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"Price\": 18.99,\r\n \"Category\": \"Pants\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "https://localhost:7265/api/Products", + "protocol": "https", + "host": [ + "localhost" + ], + "port": "7265", + "path": [ + "api", + "Products" + ] + } + }, + "response": [] + }, + { + "name": "Get All Products", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"Price\": 18.99,\r\n \"Category\": \"Pants\"\r\n}" + }, + "url": { + "raw": "https://localhost:7265/api/Products", + "protocol": "https", + "host": [ + "localhost" + ], + "port": "7265", + "path": [ + "api", + "Products" + ] + } + }, + "response": [] + }, + { + "name": "Get Product By Category", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"Price\": 18.99,\r\n \"Category\": \"Pants\"\r\n}" + }, + "url": { + "raw": "https://localhost:7265/api/Products/category/shirts", + "protocol": "https", + "host": [ + "localhost" + ], + "port": "7265", + "path": [ + "api", + "Products", + "category", + "shirts" + ] + } + }, + "response": [] + }, + { + "name": "Get Product By Id", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"Price\": 18.99,\r\n \"Category\": \"Pants\"\r\n}" + }, + "url": { + "raw": "https://localhost:7265/api/Products/id/1", + "protocol": "https", + "host": [ + "localhost" + ], + "port": "7265", + "path": [ + "api", + "Products", + "id", + "1" + ] + } + }, + "response": [] + }, + { + "name": "Sales Post", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"Products\":[1,6]\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "https://localhost:7265/api/Sales", + "protocol": "https", + "host": [ + "localhost" + ], + "port": "7265", + "path": [ + "api", + "Sales" + ] + } + }, + "response": [] + }, + { + "name": "Get All Sales", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"Products\":[\r\n {\"id\":7, \"Price\":20.99, \"Category\":\"Shirts\"},\r\n {\"id\":8, \"Price\": 12.99, \"Category\": \"Pants\"}\r\n ]\r\n}" + }, + "url": { + "raw": "https://localhost:7265/api/Sales", + "protocol": "https", + "host": [ + "localhost" + ], + "port": "7265", + "path": [ + "api", + "Sales" + ] + } + }, + "response": [] + }, + { + "name": "Get Sale By Id", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"Products\":[\r\n {\"id\":7, \"Price\":20.99, \"Category\":\"Shirts\"},\r\n {\"id\":8, \"Price\": 12.99, \"Category\": \"Pants\"}\r\n ]\r\n}" + }, + "url": { + "raw": "https://localhost:7265/api/Sales/id/1", + "protocol": "https", + "host": [ + "localhost" + ], + "port": "7265", + "path": [ + "api", + "Sales", + "id", + "1" + ] + } + }, + "response": [] + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..233e104a --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# Ecommerce.API.davetn657 + +## Overview + +A WebAPI for an ecommerce that stores and sends product, sales, and categories data + +Made for C# Academey Project based Learning + +## Requirements + +- Must use ASP.NET Core Web API and EF core +- API need to use Dependency Injection +- Should have atleast three tables (products, sales, categories) +- Products must have a price +- Multiple products can be used in the same sale +- Products and sales get endpoints must have pagination capability + +## Technologies + +- C# +- Entity Framework Core +- Sqlite + +## Looking back + +- I feel I am getting more familiar with building projects, planning and actually building them out has gotten significantly easier even with more complex projects +- For this project the only thing I struggled with was the many-to-many relationship between Sales and Products. It was difficult to understand how to implement it as I kept getting cyclical errors whenever I used either GET methods. I found that using Dtos to manage what data is being input/output fixed this for me. +- I was able to build out most of the API without needing to look back at documents and past project. +- This was my first time using pagination and it was mostly copying from the learning resource, but I did need to modify it a little to fit better into my codebase which helped me understand the process.