diff --git a/HomeWork33/.dockerignore b/HomeWork33/.dockerignore
new file mode 100644
index 0000000..cd967fc
--- /dev/null
+++ b/HomeWork33/.dockerignore
@@ -0,0 +1,25 @@
+**/.dockerignore
+**/.env
+**/.git
+**/.gitignore
+**/.project
+**/.settings
+**/.toolstarget
+**/.vs
+**/.vscode
+**/.idea
+**/*.*proj.user
+**/*.dbmdl
+**/*.jfm
+**/azds.yaml
+**/bin
+**/charts
+**/docker-compose*
+**/Dockerfile*
+**/node_modules
+**/npm-debug.log
+**/obj
+**/secrets.dev.yaml
+**/values.dev.yaml
+LICENSE
+README.md
\ No newline at end of file
diff --git a/HomeWork33/.editorconfig b/HomeWork33/.editorconfig
new file mode 100644
index 0000000..6a5b933
--- /dev/null
+++ b/HomeWork33/.editorconfig
@@ -0,0 +1,4 @@
+[*.cs]
+
+# Default severity for analyzer diagnostics with category 'Usage'
+dotnet_analyzer_diagnostic.category-Usage.severity = none
diff --git a/HomeWork33/.gitignore b/HomeWork33/.gitignore
new file mode 100644
index 0000000..a5dbce6
--- /dev/null
+++ b/HomeWork33/.gitignore
@@ -0,0 +1,288 @@
+syntax glob
+
+[Bb]inaries
+
+# Build tools related files
+[Tt]ools
+
+### VisualStudio ###
+
+# User-specific files
+.suo
+.user
+.userosscache
+.sln.docstates
+
+# Build results
+[Dd]ebug
+[Dd]ebugPublic
+[Rr]elease
+[Rr]eleases
+x64
+x86
+build
+bld
+[Bb]in
+[Oo]bj
+msbuild.log
+
+# Visual Studio 2015
+.vs
+
+# Visual Studio 2015 Pre-CTP6
+.sln.ide
+.ide
+
+# MSTest test Results
+[Tt]est[Rr]esult
+[Bb]uild[Ll]og.
+
+#NUNIT
+.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS
+[Rr]eleasePS
+dlldata.c
+
+_i.c
+_p.c
+_i.h
+.ilk
+.meta
+.obj
+.pch
+.pdb
+.pgc
+.pgd
+.rsp
+.sbr
+.tlb
+.tli
+.tlh
+.tmp
+.tmp_proj
+.log
+.html
+.vspscc
+.vssscc
+.builds
+.pidb
+.svclog
+.scc
+
+# Chutzpah Test files
+_Chutzpah
+
+# Visual C++ cache files
+ipch
+.aps
+.ncb
+.opensdf
+.sdf
+.cachefile
+
+# Visual Studio profiler
+.psess
+.vsp
+.vspx
+
+# TFS 2012 Local Workspace
+$tf
+
+# Guidance Automation Toolkit
+.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper
+.[Rr]e[Ss]harper
+.DotSettings.user
+
+# JustCode is a .NET coding addin-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity
+
+# DotCover is a Code Coverage Tool
+.dotCover
+
+# NCrunch
+_NCrunch_
+.crunch.local.xml
+
+# MightyMoose
+.mm.
+AutoTest.Net
+
+# Web workbench (sass)
+.sass-cache
+
+# Installshield output folder
+[Ee]xpress
+
+# DocProject is a documentation generator add-in
+DocProjectbuildhelp
+DocProjectHelp.HxT
+DocProjectHelp.HxC
+DocProjectHelp.hhc
+DocProjectHelp.hhk
+DocProjectHelp.hhp
+DocProjectHelpHtml2
+DocProjectHelphtml
+
+# Click-Once directory
+publish
+
+# Publish Web Output
+.[Pp]ublish.xml
+.azurePubxml
+.pubxml
+.publishproj
+
+# NuGet Packages
+.nupkg
+packages
+project.lock.json
+
+# Windows Azure Build Output
+csx
+.build.csdef
+
+# Windows Store app package directory
+AppPackages
+
+# Others
+sql
+.Cache
+ClientBin
+[Ss]tyle[Cc]op.
+~$
+.dbmdl
+.dbproj.schemaview
+.pfx
+.publishsettings
+node_modules
+.metaproj
+.metaproj.tmp
+.atom-build.json
+tags
+TAGS
+
+# RIASilverlight 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
+
+# SQL Server files
+.mdf
+.ldf
+
+# Business Intelligence projects
+.rdl.data
+.bim.layout
+.bim_.settings
+
+# Microsoft Fakes
+FakesAssemblies
+
+### MonoDevelop ###
+
+.pidb
+.userprefs
+
+### Windows ###
+
+# Windows image file caches
+Thumbs.db
+ehthumbs.db
+
+# Folder config file
+Desktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN
+
+# Windows Installer files
+.cab
+.msi
+.msm
+.msp
+
+# Windows shortcuts
+.lnk
+
+# Common binary extensions on Windows
+.exe
+.dll
+.lib
+
+### Linux ###
+
+~
+##
+
+# KDE directory preferences
+.directory
+
+### OSX ###
+
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two r
+Icon
+
+# Thumbnails
+._
+
+# Files that might appear on external disk
+.Spotlight-V100
+.Trashes
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# We have some checked in prebuilt generated files
+!srcpalprebuiltidl_i.c
+
+# Valid 'debug' folder, that contains CLR debuggin code
+!srcdebug
+
+# Ignore folders created by the test build
+TestWrappers_x64_debug
+TestWrappers_x64_checked
+TestWrappers_x64_release
+
+Vagrantfile
+.vagrant
+
+# CMake files
+CMakeFiles
+cmake_install.cmake
+CMakeCache.txt
+Makefile
+
+# Cross compilation
+crossrootfs
+
+#python import files
+.pyc
+
+.idea
+.generated-resources
+.terraform
+terraform.tfstate
+terraform.tfstate.backup
\ No newline at end of file
diff --git a/HomeWork33/Catalog.UnitTest/Catalog.UnitTest.csproj b/HomeWork33/Catalog.UnitTest/Catalog.UnitTest.csproj
new file mode 100644
index 0000000..3f24884
--- /dev/null
+++ b/HomeWork33/Catalog.UnitTest/Catalog.UnitTest.csproj
@@ -0,0 +1,37 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+ false
+ true
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/HomeWork33/Catalog.UnitTest/GlobalUsing.cs b/HomeWork33/Catalog.UnitTest/GlobalUsing.cs
new file mode 100644
index 0000000..830a113
--- /dev/null
+++ b/HomeWork33/Catalog.UnitTest/GlobalUsing.cs
@@ -0,0 +1,14 @@
+global using System;
+global using System.Collections.Generic;
+global using System.Threading.Tasks;
+global using AutoMapper;
+global using Catalog.Host.Data;
+global using Catalog.Host.Repositories.Interfaces;
+global using Catalog.Host.Services;
+global using Catalog.Host.Services.Interfaces;
+global using FluentAssertions;
+global using Infrastructure.Services.Interfaces;
+global using Microsoft.EntityFrameworkCore.Storage;
+global using Microsoft.Extensions.Logging;
+global using Moq;
+global using Xunit;
\ No newline at end of file
diff --git a/HomeWork33/Catalog.UnitTest/Services/CatalogBrandServiceTest.cs b/HomeWork33/Catalog.UnitTest/Services/CatalogBrandServiceTest.cs
new file mode 100644
index 0000000..bffe0de
--- /dev/null
+++ b/HomeWork33/Catalog.UnitTest/Services/CatalogBrandServiceTest.cs
@@ -0,0 +1,155 @@
+using Catalog.Host.Data.Entities;
+using Catalog.Host.Models.Dtos;
+
+namespace Catalog.UnitTest.Services
+{
+ public class CatalogBrandServiceTest
+ {
+ private readonly ICatalogBrandService _catalogBrandService;
+
+ private readonly Mock _brandRepository;
+ private readonly Mock> _Logger;
+ private readonly Mock _mapper;
+ private readonly Mock> _dbContextWrapper;
+ public CatalogBrandServiceTest()
+ {
+ _brandRepository = new Mock();
+ _dbContextWrapper = new Mock>();
+ _mapper = new Mock();
+ var dbContextTransaction = new Mock();
+ _dbContextWrapper.Setup(x => x.BeginTransactionAsync(CancellationToken.None)).ReturnsAsync(dbContextTransaction.Object);
+ _Logger = new Mock>();
+
+ _catalogBrandService = new CatalogBrandService(_dbContextWrapper.Object, _Logger.Object, _brandRepository.Object, _mapper.Object);
+ }
+
+ [Fact]
+ public async Task Add_Seccusfull()
+ {
+ //arrage
+ int outTest = 1;
+ string inTest = "test";
+
+ _brandRepository
+ .Setup(s => s.AddAsync(It.Is(i=> i == inTest)))
+ .ReturnsAsync(outTest);
+
+ //act
+ var responce = await _catalogBrandService.AddAsync(inTest);
+
+ //assert
+ responce.Should().Be(outTest);
+ }
+
+ [Fact]
+ public async Task Add_Failed()
+ {
+ //arrage
+ int? outTest = null;
+ string? inTest = null;
+
+ _brandRepository
+ .Setup(s => s.AddAsync(It.IsAny()))
+ .ReturnsAsync(outTest);
+
+ //act
+ var responce = await _catalogBrandService.AddAsync(inTest);
+
+ //assert
+ responce.Should().Be(outTest);
+ }
+
+
+ [Fact]
+ public async Task Delete_Succesfull()
+ {
+ //arrage
+ int? inTest = 1;
+ string? outTest = "test";
+
+ _brandRepository
+ .Setup(s=>s.DeleteAsync(It.IsAny()))
+ .ReturnsAsync(outTest);
+
+ //act
+ var responce = await _catalogBrandService.DeleteAsync(inTest);
+
+ //assert
+ responce.Should().NotBeNull();
+ }
+
+ [Fact]
+ public async Task Delete_Failed()
+ {
+ //arrage
+ int? inTest = null;
+ string? outTest = string.Empty;
+
+ _brandRepository
+ .Setup(s => s.DeleteAsync(It.IsAny()))
+ .ReturnsAsync(outTest);
+
+ //act
+ var responce = await _catalogBrandService.DeleteAsync(inTest);
+
+ //assert
+ responce.Should().NotBeNull();
+ }
+
+ [Theory]
+ [InlineData("test")]
+ public async Task Update_Succesfull(string input)
+ {
+ //arrage
+ var dto = new CatalogBrandDto()
+ {
+ Brand = input
+ };
+
+ var entity = new CatalogBrand()
+ {
+ Brand = input
+ };
+
+ _mapper.Setup(s => s.Map(It.Is(i => i.Equals(dto)))).Returns(entity);
+
+ _brandRepository
+ .Setup(s => s.UpdateAsync(It.IsAny()))
+ .ReturnsAsync(entity);
+
+ _mapper.Setup(s=>s.Map(It.Is(i=>i.Equals(entity)))).Returns(dto);
+
+ //act
+ var responce = await _catalogBrandService.UpdateAsync(dto);
+
+ //assert
+ responce.Should().NotBeNull();
+ responce.Should().Be(dto);
+ responce.Brand.Should().Be(input);
+ }
+
+ [Fact]
+ public async Task Update_Failed()
+ {
+ //arrage
+ CatalogBrandDto dto = null;
+
+ CatalogBrand entity = null;
+
+ _mapper.Setup(s => s.Map(It.Is(i => i.Equals(dto)))).Returns(entity);
+
+ _brandRepository
+ .Setup(s => s.UpdateAsync(It.IsAny()))
+ .ReturnsAsync(entity);
+
+ _mapper.Setup(s => s.Map(It.Is(i => i.Equals(entity)))).Returns(dto);
+
+ //act
+ var responce = await _catalogBrandService.UpdateAsync(dto);
+
+ //assert
+ responce.Should().BeNull();
+ responce.Should().Be(dto);
+ }
+ }
+}
diff --git a/HomeWork33/Catalog.UnitTest/Services/CatalogServiceTest.cs b/HomeWork33/Catalog.UnitTest/Services/CatalogServiceTest.cs
new file mode 100644
index 0000000..ce49c93
--- /dev/null
+++ b/HomeWork33/Catalog.UnitTest/Services/CatalogServiceTest.cs
@@ -0,0 +1,120 @@
+using Catalog.Host.Data.Entities;
+using Catalog.Host.Models.Dtos;
+using Catalog.Host.Models.Response;
+
+namespace Catalog.UnitTest.Services
+{
+ public class CatalogServiceTest
+ {
+ private readonly ICatalogService _serviceCatalog;
+
+ private readonly Mock _catalogRepository;
+ private readonly Mock _mapper;
+ private readonly Mock> _dbContextWrapper;
+ private readonly Mock> _logger;
+
+ public CatalogServiceTest()
+ {
+ _catalogRepository = new Mock();
+ _mapper = new Mock();
+ _dbContextWrapper = new Mock>();
+ _logger = new Mock>();
+
+ var dbContextTransaction = new Mock();
+ _dbContextWrapper.Setup(s => s.BeginTransactionAsync(CancellationToken.None)).ReturnsAsync(dbContextTransaction.Object);
+
+ _serviceCatalog = new CatalogService(_dbContextWrapper.Object, _logger.Object, _catalogRepository.Object, _mapper.Object);
+ }
+
+ [Fact]
+ public async Task GetByPageAsync_Succusfull()
+ {
+ //arrage
+ int pageSizeTest = 1;
+ int pageIndexTest = 5;
+ int totalCountTest = 12;
+
+ var paginationItemReponceSeccusfull = new PaginatedItems()
+ {
+ TotalCount = totalCountTest,
+ Data = new List()
+ {
+ new CatalogItem()
+ {
+ Name ="Test",
+ },
+ new CatalogItem()
+ {
+ Name ="Test",
+ AvailableStock = 5,
+ Description = "Test",
+ Price = 10,
+ CatalogBrandId = 1,
+ CatalogTypeId = 2,
+ PictureFileName = "Test"
+ },
+ new CatalogItem()
+ {
+ Name ="Test",
+ AvailableStock = 5,
+ Description = "Test",
+ Price = 10,
+ CatalogBrandId = 1,
+ CatalogTypeId = 2,
+ PictureFileName = "Test"
+ },
+ }
+ };
+
+ var catalogItemDtoSuccesfull = new CatalogItemDto()
+ {
+ Name = "Test"
+
+ };
+
+ var catalogItemSuccesfull = new CatalogItem()
+ {
+ Name = "Test"
+ };
+
+ _catalogRepository.Setup(s => s.GetByPageAsync(
+ It.Is(i => i == pageIndexTest),
+ It.Is(i => i == pageSizeTest)))
+ .ReturnsAsync(paginationItemReponceSeccusfull);
+
+ _mapper.Setup(s => s.Map(
+ It.Is(i => i.Equals(catalogItemSuccesfull))))
+ .Returns(catalogItemDtoSuccesfull); // check mapper failed
+
+ //act
+ var reesponce = await _serviceCatalog.GetByPageAsync(pageSizeTest, pageIndexTest);
+
+ //assert
+ reesponce.Should().NotBeNull();
+ reesponce.Count.Should().Be(totalCountTest);
+ reesponce.Data.Should().NotBeNull();
+ //reesponce.Data.First().Should().NotBeNull();
+ reesponce.PageIndex.Should().Be(pageIndexTest);
+ reesponce.PageSize.Should().Be(pageSizeTest);
+ }
+
+ [Fact]
+ public async Task GetByPageAsync_Failed()
+ {
+ //arrage
+ var pageIndexTest = 2000;
+ var pageSizeTest = 1000;
+
+ _catalogRepository.Setup(s => s.GetByPageAsync(
+ It.Is(i => i == pageIndexTest),
+ It.Is(i => i == pageSizeTest)))
+ .Returns((Func>)null!);
+
+ //act
+ var responce = await _serviceCatalog.GetByPageAsync(pageSizeTest, pageIndexTest);
+
+ //assert
+ responce.Should().BeNull();
+ }
+ }
+}
diff --git a/HomeWork33/Catalog.UnitTest/Services/CatalogTypeServiceTest.cs b/HomeWork33/Catalog.UnitTest/Services/CatalogTypeServiceTest.cs
new file mode 100644
index 0000000..2479e65
--- /dev/null
+++ b/HomeWork33/Catalog.UnitTest/Services/CatalogTypeServiceTest.cs
@@ -0,0 +1,148 @@
+using Catalog.Host.Data.Entities;
+using Catalog.Host.Models.Dtos;
+
+namespace Catalog.UnitTest.Services
+{
+ public class CatalogTypeServiceTest
+ {
+ private readonly Mock _catalogTypeRepository;
+ private readonly Mock> _logger;
+ private readonly Mock> _dbContextWrapper;
+ private readonly Mock _mapper;
+
+ private readonly CatalogTypeService _catalogTypeService;
+
+ public CatalogTypeServiceTest()
+ {
+ _catalogTypeRepository = new Mock();
+ _logger = new Mock>();
+ _mapper = new Mock();
+ _dbContextWrapper = new Mock>();
+
+ var dbContextTransaction = new Mock();
+ _dbContextWrapper
+ .Setup(s => s.BeginTransactionAsync(CancellationToken.None))
+ .ReturnsAsync(dbContextTransaction.Object);
+
+ _catalogTypeService = new CatalogTypeService(
+ _catalogTypeRepository.Object,
+ _mapper.Object,
+ _dbContextWrapper.Object,
+ _logger.Object);
+ }
+
+ [Fact]
+ public async Task Add_Seccusfull()
+ {
+ //arrage
+ var inTest = "test";
+ var outTest = 1;
+
+ _catalogTypeRepository.Setup(s => s.AddTypeAsync(It.IsAny())).ReturnsAsync(outTest);
+
+ //act
+ var responce = await _catalogTypeService.AddType(inTest);
+
+ //assert
+ responce.Should().Be(outTest);
+ }
+
+ [Fact]
+ public async Task Add_Failed()
+ {
+ //arrage
+ string? inTest = null;
+ int? outTest = null;
+
+ _catalogTypeRepository.Setup(s => s.AddTypeAsync(It.IsAny())).ReturnsAsync(outTest);
+
+ //act
+ var responce = await _catalogTypeService.AddType(inTest);
+
+ //assert
+ responce.Should().Be(outTest);
+ }
+
+ [Fact]
+ public async Task Delete_succesfull()
+ {
+ //arrage
+ var inTest = 1;
+ var outTest = "test";
+
+ _catalogTypeRepository.Setup(s=>s.DeleteType(It.IsAny())).ReturnsAsync(outTest);
+
+ //act
+ var responce = await _catalogTypeService.DeleteType(inTest);
+
+ //assert
+ responce.Should().Be(outTest);
+ }
+
+ public async Task Delete_failed()
+ {
+ //arrage
+ int? inTest = null;
+ string? outTest = null;
+
+ _catalogTypeRepository.Setup(s => s.DeleteType(It.IsAny())).ReturnsAsync(outTest);
+
+ //act
+ var responce = await _catalogTypeService.DeleteType(inTest);
+
+ //assert
+ responce.Should().Be(outTest);
+ }
+
+ [Fact]
+ public async Task Update_Succesfull()
+ {
+ //arrage
+ var dto = new CatalogTypeDto()
+ {
+ Type = "test"
+ };
+ var entity = new CatalogType()
+ {
+ Type= "test"
+ };
+
+ _mapper.Setup(s => s.Map(It.Is(i => i.Equals(dto)))).Returns(entity);
+
+ _catalogTypeRepository
+ .Setup(s => s.Update(It.Is(i => i.Equals(entity))))
+ .ReturnsAsync(entity);
+
+ _mapper.Setup(s => s.Map(It.Is(i => i.Equals(entity)))).Returns(dto);
+
+ //act
+ var responce = await _catalogTypeService.UpdateType(dto);
+
+ //assert
+ responce.Should().NotBeNull();
+ responce.Type.Should().Be("test");
+ }
+
+ [Fact]
+ public async Task Update_Failed()
+ {
+ //arrage
+ CatalogTypeDto? dto = null;
+ CatalogType? entity = null;
+
+ _mapper.Setup(s => s.Map(It.Is(i => i.Equals(dto)))).Returns(entity);
+
+ _catalogTypeRepository
+ .Setup(s => s.Update(It.Is(i => i.Equals(entity))))
+ .ReturnsAsync(entity);
+
+ _mapper.Setup(s => s.Map(It.Is(i => i.Equals(entity)))).Returns(dto);
+
+ //act
+ var responce = await _catalogTypeService.UpdateType(dto);
+
+ //assert
+ responce.Should().BeNull();
+ }
+ }
+}
diff --git a/HomeWork33/Catalog.UnitTest/Services/CatalogitemServiceTest.cs b/HomeWork33/Catalog.UnitTest/Services/CatalogitemServiceTest.cs
new file mode 100644
index 0000000..718661a
--- /dev/null
+++ b/HomeWork33/Catalog.UnitTest/Services/CatalogitemServiceTest.cs
@@ -0,0 +1,420 @@
+using Catalog.Host.Data.Entities;
+using Catalog.Host.Models.Dtos;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+
+namespace Catalog.UnitTest.Services
+{
+ public class CatalogitemServiceTest
+ {
+ private readonly ICatalogItemService _catalogItemService;
+
+ private readonly Mock _catalogItemRepository;
+ private readonly Mock> _dbContextWrapper;
+ private readonly Mock> _logger;
+ private readonly Mock _mapper;
+
+ private readonly CatalogItem _testItem = new CatalogItem()
+ {
+ Name = "NameTest",
+ Description = "DescriptionTest",
+ Price = 1000,
+ AvailableStock = 100,
+ CatalogBrandId = 1,
+ CatalogTypeId = 1,
+ PictureFileName = "1.pngTest"
+ };
+
+ public CatalogitemServiceTest()
+ {
+ _catalogItemRepository = new Mock();
+ _dbContextWrapper = new Mock>();
+ _logger = new Mock>();
+ _mapper = new Mock();
+
+ var dbContextTransaction = new Mock();
+ _dbContextWrapper.Setup(s => s.BeginTransactionAsync(CancellationToken.None)).ReturnsAsync(dbContextTransaction.Object);
+
+ _catalogItemService = new CatalogItemService(_dbContextWrapper.Object, _logger.Object, _catalogItemRepository.Object, _mapper.Object);
+ }
+
+ [Fact]
+ public async Task AddAsync_Seccusfull()
+ {
+ //arrage
+ var testResult = 1;
+
+ _catalogItemRepository
+ .Setup(s => s.Add(
+ It.IsAny(),
+ It.IsAny(),
+ It.IsAny(),
+ It.IsAny(),
+ It.IsAny(),
+ It.IsAny(),
+ It.IsAny()))
+ .ReturnsAsync(testResult);
+
+ //act
+ var result = await _catalogItemService
+ .Add(
+ _testItem.Name,
+ _testItem.Description,
+ _testItem.Price,
+ _testItem.AvailableStock,
+ _testItem.CatalogBrandId,
+ _testItem.CatalogTypeId,
+ _testItem.PictureFileName
+ );
+
+ //asert
+ result.Should().Be(testResult);
+ }
+
+ [Fact]
+ public async Task AddAsync_Failed()
+ {
+ //arrage
+ int? testResult = null;
+
+ _catalogItemRepository
+ .Setup(s => s.Add(
+ It.IsAny(),
+ It.IsAny(),
+ It.IsAny(),
+ It.IsAny(),
+ It.IsAny(),
+ It.IsAny(),
+ It.IsAny()))
+ .ReturnsAsync(testResult);
+
+ //act
+ var result = await _catalogItemService
+ .Add(
+ _testItem.Name,
+ _testItem.Description,
+ _testItem.Price,
+ _testItem.AvailableStock,
+ _testItem.CatalogBrandId,
+ _testItem.CatalogTypeId,
+ _testItem.PictureFileName
+ );
+
+ //asert
+ result.Should().Be(testResult);
+ }
+
+ [Fact]
+ public async Task GetCatalogItemsByIdAsync_Succesfull()
+ {
+ //arrage
+ int id = 1;
+
+ var catalogItemDtoSuccesfull = new CatalogItemDto()
+ {
+ Name = "Test",
+ AvailableStock = 5,
+ Description = "Test",
+ Price = 10,
+ PictureUrl = "Test",
+ Id = 1
+ };
+
+ var catalogItemSuccesfull = new CatalogItem()
+ {
+ Name = "Test",
+ AvailableStock = 5,
+ Description = "Test",
+ Price = 10,
+ CatalogBrandId = 1,
+ CatalogTypeId = 2,
+ Id = 1
+ };
+
+ _catalogItemRepository
+ .Setup(s => s.GetCatalogItemsByIdAsync(It.IsAny()))
+ .ReturnsAsync(catalogItemSuccesfull);
+
+ _mapper.Setup(s => s.Map(
+ It.Is(i => i.Equals(catalogItemSuccesfull))))
+ .Returns(catalogItemDtoSuccesfull);
+
+ //act
+ var responce = await _catalogItemService.GetCatalogItemsByIdAsync(id);
+
+ //asert
+ responce.Should().NotBeNull();
+ responce.Equals(catalogItemDtoSuccesfull);
+ }
+
+ [Fact]
+ public async Task GetCatalogItemsByIdAsync_Failedl()
+ {
+ //arrage
+ int? id = null;
+
+ _catalogItemRepository
+ .Setup(s => s.GetCatalogItemsByIdAsync(It.IsAny()))
+ .Returns((Func)null!);
+
+ //act
+ var responce = await _catalogItemService.GetCatalogItemsByIdAsync(id);
+
+ //asert
+ responce.Should().BeNull();
+ }
+
+ [Fact]
+ public async Task Delete_Seccusfull()
+ {
+ //arrage
+ int id = 1;
+
+ var answer = "text";
+
+ _catalogItemRepository
+ .Setup(s => s.DeleteAsync(It.IsAny()))
+ .ReturnsAsync(answer);
+
+ //act
+ var reponce = await _catalogItemService.DeleteAsync(id);
+
+ //asert
+ reponce.Should().NotBeNull();
+ reponce.Equals(answer);
+ }
+
+ [Fact]
+ public async Task Delete_Failed()
+ {
+ //arrage
+ int? id = null!;
+
+ string? answer = "";
+
+ _catalogItemRepository
+ .Setup(s => s.DeleteAsync(It.IsAny()))
+ .ReturnsAsync(answer);
+
+ //act
+ var reponce = await _catalogItemService.DeleteAsync(id);
+
+ //asert
+ reponce.Should().NotBeNull();
+ reponce.Equals(answer);
+ }
+
+ [Fact]
+ public async Task Update_Succesfull()
+ {
+ //arrage
+ var catalogItemDtoSuccesfull = new CatalogItemDto()
+ {
+ Name = "Test",
+ AvailableStock = 5,
+ Description = "Test",
+ Price = 10,
+ PictureUrl = "Test",
+ Id = 1
+ };
+
+ var catalogItemSuccesfull = new CatalogItem()
+ {
+ Name = "Test",
+ AvailableStock = 5,
+ Description = "Test",
+ Price = 10,
+ CatalogBrandId = 1,
+ CatalogTypeId = 2,
+ Id = 1
+ };
+
+ _mapper
+ .Setup(s =>
+ s.Map(It.Is(i =>
+ i.Equals(catalogItemDtoSuccesfull))))
+ .Returns(catalogItemSuccesfull);
+
+ _catalogItemRepository
+ .Setup(s => s.Update(It.IsAny()))
+ .ReturnsAsync(catalogItemSuccesfull);
+
+
+ _mapper
+ .Setup(s =>
+ s.Map(It.Is(i =>
+ i.Equals(catalogItemSuccesfull))))
+ .Returns(catalogItemDtoSuccesfull);
+
+ //act
+ var reponce = await _catalogItemService
+ .UpdateAsync(catalogItemDtoSuccesfull);
+
+ //asert
+ reponce.Should().NotBeNull();
+ reponce.Description.Should().Be("Test");
+ }
+
+ [Fact]
+ public async Task Update_Failed()
+ {
+ //arrage
+
+ CatalogItemDto catalog = null;
+
+ _catalogItemRepository
+ .Setup(s => s.Update(It.IsAny()))
+ .Returns((Func)null!);
+
+ //act
+ var reponce = await _catalogItemService.UpdateAsync(catalog);
+
+ //asert
+ reponce.Should().BeNull();
+ }
+
+ [Fact]
+ public async Task GetCatalogItemByTypeAsync_Succesfull()
+ {
+ //arrage
+ var id = 1;
+ var list = new List()
+ {
+ new CatalogItem()
+ {
+ Name ="Test",
+ },
+ new CatalogItem()
+ {
+ Name ="Test",
+ AvailableStock = 5,
+ Description = "Test",
+ Price = 10,
+ CatalogBrandId = 1,
+ CatalogTypeId = 2,
+ PictureFileName = "Test"
+ },
+ new CatalogItem()
+ {
+ Name ="Test",
+ AvailableStock = 5,
+ Description = "Test",
+ Price = 10,
+ CatalogBrandId = 1,
+ CatalogTypeId = 2,
+ PictureFileName = "Test"
+ },
+ };
+
+ var dto = new CatalogItemDto()
+ {
+ Name = "Test",
+ AvailableStock = 5,
+ Description = "Test",
+ Price = 10,
+ CatalogBrand = new CatalogBrandDto() { Brand = "test" },
+ CatalogType = new CatalogTypeDto() { Type = "test" },
+
+ };
+
+ _mapper.Setup(s => s.Map(It.IsAny())).Returns(dto);
+
+ _catalogItemRepository
+ .Setup(s => s.GetCatalogItemsByTypeAsync(It.IsAny()))
+ .ReturnsAsync(list);
+
+ //act
+ var reponce = await _catalogItemService.GetCatalogItemByTypeAsync(id);
+
+ //asert
+ reponce.Should().NotBeNull();
+ }
+
+ [Fact]
+ public async Task GetCatalogItemByTypeAsync_Failed()
+ {
+ //arrage
+ int? id = null;
+ List? list = null;
+
+ CatalogItemDto? dto = null;
+
+ _mapper.Setup(s => s.Map(It.IsAny())).Returns(dto);
+
+ _catalogItemRepository
+ .Setup(s => s.GetCatalogItemsByTypeAsync(It.IsAny()))
+ .ReturnsAsync(list);
+
+ //act
+ var reponce = await _catalogItemService.GetCatalogItemByTypeAsync(id);
+
+ //asert
+ reponce.Should().BeNull();
+ }
+
+ [Fact]
+ public async Task GetCatalogItemByandAsync_Succesfull()
+ {
+ //arrage
+ var id = 1;
+ var list = new List()
+ {
+ new CatalogItem()
+ {
+ Name ="Test",
+ },
+ new CatalogItem()
+ {
+ Name ="Test",
+ },
+ new CatalogItem()
+ {
+ Name ="Test",
+ },
+ };
+
+ var dto = new CatalogItemDto()
+ {
+ Name = "Test",
+ AvailableStock = 5,
+ Description = "Test",
+ Price = 10,
+ CatalogBrand = new CatalogBrandDto() { Brand = "test" },
+ CatalogType = new CatalogTypeDto() { Type = "test" },
+
+ };
+
+ _mapper.Setup(s => s.Map(It.IsAny())).Returns(dto);
+
+ _catalogItemRepository
+ .Setup(s => s.GetCatalogItemsByBrandAsync(It.IsAny()))
+ .ReturnsAsync(list);
+
+ //act
+ var reponce = await _catalogItemService.GetCatalogItemByBrandAsync(id);
+
+ //asert
+ reponce.Should().NotBeNull();
+ }
+ [Fact]
+ public async Task GetCatalogItemByBrandAsync_Failed()
+ {
+ //arrage
+ int? id = null;
+ List? list = null;
+
+ CatalogItemDto? dto = null;
+
+ _mapper.Setup(s => s.Map(It.IsAny())).Returns(dto);
+
+ _catalogItemRepository
+ .Setup(s => s.GetCatalogItemsByBrandAsync(It.IsAny()))
+ .ReturnsAsync(list);
+
+ //act
+ var reponce = await _catalogItemService.GetCatalogItemByBrandAsync(id);
+
+ //asert
+ reponce.Should().BeNull();
+ }
+ }
+}
diff --git a/HomeWork33/Catalog/Catalog.Host/.dockerignore b/HomeWork33/Catalog/Catalog.Host/.dockerignore
new file mode 100644
index 0000000..cd967fc
--- /dev/null
+++ b/HomeWork33/Catalog/Catalog.Host/.dockerignore
@@ -0,0 +1,25 @@
+**/.dockerignore
+**/.env
+**/.git
+**/.gitignore
+**/.project
+**/.settings
+**/.toolstarget
+**/.vs
+**/.vscode
+**/.idea
+**/*.*proj.user
+**/*.dbmdl
+**/*.jfm
+**/azds.yaml
+**/bin
+**/charts
+**/docker-compose*
+**/Dockerfile*
+**/node_modules
+**/npm-debug.log
+**/obj
+**/secrets.dev.yaml
+**/values.dev.yaml
+LICENSE
+README.md
\ No newline at end of file
diff --git a/HomeWork33/Catalog/Catalog.Host/Catalog.Host.csproj b/HomeWork33/Catalog/Catalog.Host/Catalog.Host.csproj
new file mode 100644
index 0000000..61e5aa5
--- /dev/null
+++ b/HomeWork33/Catalog/Catalog.Host/Catalog.Host.csproj
@@ -0,0 +1,41 @@
+
+
+
+ net8.0
+ enable
+ enable
+ Linux
+ ../../settings.ruleset
+ true
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/HomeWork33/Catalog/Catalog.Host/Catalog.Host.csproj.user b/HomeWork33/Catalog/Catalog.Host/Catalog.Host.csproj.user
new file mode 100644
index 0000000..46b1b35
--- /dev/null
+++ b/HomeWork33/Catalog/Catalog.Host/Catalog.Host.csproj.user
@@ -0,0 +1,12 @@
+
+
+
+ ProjectDebugger
+
+
+ Catalog.Host
+
+
+ ProjectDebugger
+
+
\ No newline at end of file
diff --git a/HomeWork33/Catalog/Catalog.Host/Configurations/CatalogConfig.cs b/HomeWork33/Catalog/Catalog.Host/Configurations/CatalogConfig.cs
new file mode 100644
index 0000000..36df229
--- /dev/null
+++ b/HomeWork33/Catalog/Catalog.Host/Configurations/CatalogConfig.cs
@@ -0,0 +1,9 @@
+#pragma warning disable CS8618
+namespace Catalog.Host.Configurations;
+
+public class CatalogConfig
+{
+ public string Host { get; set; }
+ public string ImgUrl { get; set; }
+ public string ConnectionString { get; set; }
+}
\ No newline at end of file
diff --git a/HomeWork33/Catalog/Catalog.Host/Controllers/CatalogBffController.cs b/HomeWork33/Catalog/Catalog.Host/Controllers/CatalogBffController.cs
new file mode 100644
index 0000000..3eb862e
--- /dev/null
+++ b/HomeWork33/Catalog/Catalog.Host/Controllers/CatalogBffController.cs
@@ -0,0 +1,61 @@
+using System.Net;
+using Catalog.Host.Data.Entities;
+using Catalog.Host.Models.Dtos;
+using Catalog.Host.Models.Requests;
+using Catalog.Host.Models.Response;
+using Catalog.Host.Services.Interfaces;
+using Infrastructure;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Catalog.Host.Controllers;
+
+[ApiController]
+[Route(ComponentDefaults.DefaultRoute)]
+public class CatalogBffController : ControllerBase
+{
+ private readonly ILogger _logger;
+ private readonly ICatalogService _catalogService;
+ private readonly ICatalogItemService _catalogItemService;
+ public CatalogBffController(
+ ILogger logger,
+ ICatalogService catalogService,
+ ICatalogItemService catalogItemService)
+ {
+ _logger = logger;
+ _catalogService = catalogService;
+ _catalogItemService = catalogItemService;
+
+ }
+
+ [HttpPost]
+ [ProducesResponseType(typeof(PaginatedItemsResponse), (int)HttpStatusCode.OK)]
+ public async Task Items(PaginatedItemsRequest request)
+ {
+ var result = await _catalogService.GetByPageAsync(request.PageSize, request.PageIndex);
+ return Ok(result);
+ }
+
+ [HttpPost]
+ [ProducesResponseType(typeof(PaginatedItemsResponse), (int)HttpStatusCode.OK)]
+ public async Task GetById(int id)
+ {
+ var result = await _catalogItemService.GetCatalogItemsByIdAsync(id);
+ return Ok(result);
+ }
+
+ [HttpPost]
+ [ProducesResponseType(typeof(PaginatedItemsResponse), (int)HttpStatusCode.OK)]
+ public async Task GetByBrand(int idBrand)
+ {
+ var result = await _catalogItemService.GetCatalogItemByBrandAsync(idBrand);
+ return Ok(result);
+ }
+
+ [HttpPost]
+ [ProducesResponseType(typeof(PaginatedItemsResponse), (int)HttpStatusCode.OK)]
+ public async Task GetByType(int idType)
+ {
+ var result = await _catalogItemService.GetCatalogItemByTypeAsync(idType);
+ return Ok(result);
+ }
+}
\ No newline at end of file
diff --git a/HomeWork33/Catalog/Catalog.Host/Controllers/CatalogBrandController.cs b/HomeWork33/Catalog/Catalog.Host/Controllers/CatalogBrandController.cs
new file mode 100644
index 0000000..f066bb0
--- /dev/null
+++ b/HomeWork33/Catalog/Catalog.Host/Controllers/CatalogBrandController.cs
@@ -0,0 +1,51 @@
+using Catalog.Host.Data.Entities;
+using Catalog.Host.Models.Dtos;
+using Catalog.Host.Services.Interfaces;
+using Infrastructure;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Catalog.Host.Controllers;
+
+[ApiController]
+[Route(ComponentDefaults.DefaultRoute)]
+public class CatalogBrandController : ControllerBase
+{
+ private readonly ILogger _logger;
+ private readonly ICatalogBrandService _service;
+
+ public CatalogBrandController(ILogger logger, ICatalogBrandService catalogBrandService)
+ {
+ _logger = logger;
+ _service = catalogBrandService;
+ }
+
+ [HttpPost]
+ public async Task AddBrand(string? brand)
+ {
+ if (brand is null)
+ {
+ return null;
+ }
+ return await _service.AddAsync(brand);
+ }
+
+ [HttpPut]
+ public async Task UpdateBrand(CatalogBrandDto? catalogBrand)
+ {
+ if(catalogBrand is null)
+ {
+ return null;
+ }
+ return await _service.UpdateAsync(catalogBrand);
+ }
+
+ [HttpDelete]
+ public async Task DeleteBrand(int? id)
+ {
+ if (id is null)
+ {
+ return null;
+ }
+ return await _service.DeleteAsync(id);
+ }
+}
\ No newline at end of file
diff --git a/HomeWork33/Catalog/Catalog.Host/Controllers/CatalogItemController.cs b/HomeWork33/Catalog/Catalog.Host/Controllers/CatalogItemController.cs
new file mode 100644
index 0000000..1806a55
--- /dev/null
+++ b/HomeWork33/Catalog/Catalog.Host/Controllers/CatalogItemController.cs
@@ -0,0 +1,59 @@
+using System.Net;
+using Catalog.Host.Models.Dtos;
+using Catalog.Host.Models.Requests;
+using Catalog.Host.Models.Response;
+using Catalog.Host.Services.Interfaces;
+using Infrastructure;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Catalog.Host.Controllers;
+
+[ApiController]
+[Route(ComponentDefaults.DefaultRoute)]
+public class CatalogItemController : ControllerBase
+{
+ private readonly ILogger _logger;
+ private readonly ICatalogItemService _catalogItemService;
+
+ public CatalogItemController(
+ ILogger logger,
+ ICatalogItemService catalogItemService)
+ {
+ _logger = logger;
+ _catalogItemService = catalogItemService;
+ }
+
+ [HttpPost]
+ [ProducesResponseType(typeof(AddItemResponse), (int)HttpStatusCode.OK)]
+ public async Task Add(CreateProductRequest request)
+ {
+ var result = await _catalogItemService.Add(
+ request.Name,
+ request.Description,
+ request.Price,
+ request.AvailableStock,
+ request.CatalogBrandId,
+ request.CatalogTypeId,
+ request.PictureFileName);
+
+ return Ok(new AddItemResponse() { Id = result });
+ }
+
+ [HttpGet]
+ public async Task GetById(int id)
+ {
+ return await _catalogItemService.GetCatalogItemsByIdAsync(id);
+ }
+
+ [HttpDelete]
+ public async Task Delete(int id)
+ {
+ return await _catalogItemService.DeleteAsync(id);
+ }
+
+ [HttpPut]
+ public async Task Update(CatalogItemDto catalogItemDto)
+ {
+ return await _catalogItemService.UpdateAsync(catalogItemDto);
+ }
+}
\ No newline at end of file
diff --git a/HomeWork33/Catalog/Catalog.Host/Controllers/CatalogTypeController.cs b/HomeWork33/Catalog/Catalog.Host/Controllers/CatalogTypeController.cs
new file mode 100644
index 0000000..759f75a
--- /dev/null
+++ b/HomeWork33/Catalog/Catalog.Host/Controllers/CatalogTypeController.cs
@@ -0,0 +1,56 @@
+using Catalog.Host.Data.Entities;
+using Catalog.Host.Models.Dtos;
+using Catalog.Host.Services.Interfaces;
+using Infrastructure;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Catalog.Host.Controllers;
+
+[ApiController]
+[Route(ComponentDefaults.DefaultRoute)]
+public class CatalogTypeController : ControllerBase
+{
+ private readonly ILogger _logger;
+ private readonly ICatalogTypeService _serivice;
+
+ public CatalogTypeController(
+ ILogger logger,
+ ICatalogTypeService service)
+ {
+ _logger = logger;
+ _serivice = service;
+ }
+
+ [HttpPost]
+ public async Task AddType(string? type)
+ {
+ if(type is null)
+ {
+ return null;
+ }
+
+ return await _serivice.AddType(type);
+ }
+
+ [HttpPut]
+ public async Task UpdateType(CatalogTypeDto? catalogType)
+ {
+ if (catalogType is null)
+ {
+ return null;
+ }
+
+ return await _serivice.UpdateType(catalogType);
+ }
+
+ [HttpDelete]
+ public async Task DeleteType(int? id)
+ {
+ if (id is null)
+ {
+ return "id can`t be null";
+ }
+
+ return await _serivice.DeleteType(id);
+ }
+}
\ No newline at end of file
diff --git a/HomeWork33/Catalog/Catalog.Host/Data/ApplicationDbContext.cs b/HomeWork33/Catalog/Catalog.Host/Data/ApplicationDbContext.cs
new file mode 100644
index 0000000..0e3750f
--- /dev/null
+++ b/HomeWork33/Catalog/Catalog.Host/Data/ApplicationDbContext.cs
@@ -0,0 +1,25 @@
+#pragma warning disable CS8618
+using Catalog.Host.Data.Entities;
+using Catalog.Host.Data.EntityConfigurations;
+using Microsoft.EntityFrameworkCore;
+
+namespace Catalog.Host.Data;
+
+public class ApplicationDbContext : DbContext
+{
+ public ApplicationDbContext(DbContextOptions options)
+ : base(options)
+ {
+ }
+
+ public DbSet CatalogItems { get; set; }
+ public DbSet CatalogBrands { get; set; }
+ public DbSet CatalogTypes { get; set; }
+
+ protected override void OnModelCreating(ModelBuilder builder)
+ {
+ builder.ApplyConfiguration(new CatalogBrandEntityTypeConfiguration());
+ builder.ApplyConfiguration(new CatalogTypeEntityTypeConfiguration());
+ builder.ApplyConfiguration(new CatalogItemEntityTypeConfiguration());
+ }
+}
diff --git a/HomeWork33/Catalog/Catalog.Host/Data/DbInitializer.cs b/HomeWork33/Catalog/Catalog.Host/Data/DbInitializer.cs
new file mode 100644
index 0000000..5f63250
--- /dev/null
+++ b/HomeWork33/Catalog/Catalog.Host/Data/DbInitializer.cs
@@ -0,0 +1,74 @@
+using Catalog.Host.Data.Entities;
+
+namespace Catalog.Host.Data;
+
+public static class DbInitializer
+{
+ public static async Task Initialize(ApplicationDbContext context)
+ {
+ await context.Database.EnsureCreatedAsync();
+
+ if (!context.CatalogBrands.Any())
+ {
+ await context.CatalogBrands.AddRangeAsync(GetPreconfiguredCatalogBrands());
+
+ await context.SaveChangesAsync();
+ }
+
+ if (!context.CatalogTypes.Any())
+ {
+ await context.CatalogTypes.AddRangeAsync(GetPreconfiguredCatalogTypes());
+
+ await context.SaveChangesAsync();
+ }
+
+ if (!context.CatalogItems.Any())
+ {
+ await context.CatalogItems.AddRangeAsync(GetPreconfiguredItems());
+
+ await context.SaveChangesAsync();
+ }
+ }
+
+ private static IEnumerable GetPreconfiguredCatalogBrands()
+ {
+ return new List()
+ {
+ new CatalogBrand() { Brand = "Azure" },
+ new CatalogBrand() { Brand = ".NET" },
+ new CatalogBrand() { Brand = "Visual Studio" },
+ new CatalogBrand() { Brand = "SQL Server" },
+ new CatalogBrand() { Brand = "Other" }
+ };
+ }
+
+ private static IEnumerable GetPreconfiguredCatalogTypes()
+ {
+ return new List()
+ {
+ new CatalogType() { Type = "Mug" },
+ new CatalogType() { Type = "T-Shirt" },
+ new CatalogType() { Type = "Sheet" },
+ new CatalogType() { Type = "USB Memory Stick" }
+ };
+ }
+
+ private static IEnumerable GetPreconfiguredItems()
+ {
+ return new List()
+ {
+ new CatalogItem { CatalogTypeId = 2, CatalogBrandId = 2, AvailableStock = 100, Description = ".NET Bot Black Hoodie", Name = ".NET Bot Black Hoodie", Price = 19.5M, PictureFileName = "1.png" },
+ new CatalogItem { CatalogTypeId = 1, CatalogBrandId = 2, AvailableStock = 100, Description = ".NET Black & White Mug", Name = ".NET Black & White Mug", Price = 8.50M, PictureFileName = "2.png" },
+ new CatalogItem { CatalogTypeId = 2, CatalogBrandId = 5, AvailableStock = 100, Description = "Prism White T-Shirt", Name = "Prism White T-Shirt", Price = 12, PictureFileName = "3.png" },
+ new CatalogItem { CatalogTypeId = 2, CatalogBrandId = 2, AvailableStock = 100, Description = ".NET Foundation T-shirt", Name = ".NET Foundation T-shirt", Price = 12, PictureFileName = "4.png" },
+ new CatalogItem { CatalogTypeId = 3, CatalogBrandId = 5, AvailableStock = 100, Description = "Roslyn Red Sheet", Name = "Roslyn Red Sheet", Price = 8.5M, PictureFileName = "5.png" },
+ new CatalogItem { CatalogTypeId = 2, CatalogBrandId = 2, AvailableStock = 100, Description = ".NET Blue Hoodie", Name = ".NET Blue Hoodie", Price = 12, PictureFileName = "6.png" },
+ new CatalogItem { CatalogTypeId = 2, CatalogBrandId = 5, AvailableStock = 100, Description = "Roslyn Red T-Shirt", Name = "Roslyn Red T-Shirt", Price = 12, PictureFileName = "7.png" },
+ new CatalogItem { CatalogTypeId = 2, CatalogBrandId = 5, AvailableStock = 100, Description = "Kudu Purple Hoodie", Name = "Kudu Purple Hoodie", Price = 8.5M, PictureFileName = "8.png" },
+ new CatalogItem { CatalogTypeId = 1, CatalogBrandId = 5, AvailableStock = 100, Description = "Cup White Mug", Name = "Cup White Mug", Price = 12, PictureFileName = "9.png" },
+ new CatalogItem { CatalogTypeId = 3, CatalogBrandId = 2, AvailableStock = 100, Description = ".NET Foundation Sheet", Name = ".NET Foundation Sheet", Price = 12, PictureFileName = "10.png" },
+ new CatalogItem { CatalogTypeId = 3, CatalogBrandId = 2, AvailableStock = 100, Description = "Cup Sheet", Name = "Cup Sheet", Price = 8.5M, PictureFileName = "11.png" },
+ new CatalogItem { CatalogTypeId = 2, CatalogBrandId = 5, AvailableStock = 100, Description = "Prism White TShirt", Name = "Prism White TShirt", Price = 12, PictureFileName = "12.png" },
+ };
+ }
+}
\ No newline at end of file
diff --git a/HomeWork33/Catalog/Catalog.Host/Data/Entities/CatalogBrand.cs b/HomeWork33/Catalog/Catalog.Host/Data/Entities/CatalogBrand.cs
new file mode 100644
index 0000000..9f63c1f
--- /dev/null
+++ b/HomeWork33/Catalog/Catalog.Host/Data/Entities/CatalogBrand.cs
@@ -0,0 +1,9 @@
+#pragma warning disable CS8618
+namespace Catalog.Host.Data.Entities;
+
+public class CatalogBrand
+{
+ public int Id { get; set; }
+
+ public string Brand { get; set; }
+}
\ No newline at end of file
diff --git a/HomeWork33/Catalog/Catalog.Host/Data/Entities/CatalogItem.cs b/HomeWork33/Catalog/Catalog.Host/Data/Entities/CatalogItem.cs
new file mode 100644
index 0000000..959f5c0
--- /dev/null
+++ b/HomeWork33/Catalog/Catalog.Host/Data/Entities/CatalogItem.cs
@@ -0,0 +1,26 @@
+#pragma warning disable CS8618
+
+namespace Catalog.Host.Data.Entities;
+
+public class CatalogItem
+{
+ public int Id { get; set; }
+
+ public string Name { get; set; }
+
+ public string Description { get; set; }
+
+ public decimal Price { get; set; }
+
+ public string PictureFileName { get; set; }
+
+ public int CatalogTypeId { get; set; }
+
+ public CatalogType CatalogType { get; set; }
+
+ public int CatalogBrandId { get; set; }
+
+ public CatalogBrand CatalogBrand { get; set; }
+
+ public int AvailableStock { get; set; }
+}
diff --git a/HomeWork33/Catalog/Catalog.Host/Data/Entities/CatalogType.cs b/HomeWork33/Catalog/Catalog.Host/Data/Entities/CatalogType.cs
new file mode 100644
index 0000000..5eaddda
--- /dev/null
+++ b/HomeWork33/Catalog/Catalog.Host/Data/Entities/CatalogType.cs
@@ -0,0 +1,9 @@
+#pragma warning disable CS8618
+namespace Catalog.Host.Data.Entities;
+
+public class CatalogType
+{
+ public int Id { get; set; }
+
+ public string Type { get; set; }
+}
\ No newline at end of file
diff --git a/HomeWork33/Catalog/Catalog.Host/Data/EntityConfigurations/CatalogBrandEntityTypeConfiguration.cs b/HomeWork33/Catalog/Catalog.Host/Data/EntityConfigurations/CatalogBrandEntityTypeConfiguration.cs
new file mode 100644
index 0000000..2ea4be3
--- /dev/null
+++ b/HomeWork33/Catalog/Catalog.Host/Data/EntityConfigurations/CatalogBrandEntityTypeConfiguration.cs
@@ -0,0 +1,24 @@
+using Catalog.Host.Data.Entities;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace Catalog.Host.Data.EntityConfigurations;
+
+public class CatalogBrandEntityTypeConfiguration
+ : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder builder)
+ {
+ builder.ToTable("CatalogBrand");
+
+ builder.HasKey(ci => ci.Id);
+
+ builder.Property(ci => ci.Id)
+ .UseHiLo("catalog_brand_hilo")
+ .IsRequired();
+
+ builder.Property(cb => cb.Brand)
+ .IsRequired()
+ .HasMaxLength(100);
+ }
+}
\ No newline at end of file
diff --git a/HomeWork33/Catalog/Catalog.Host/Data/EntityConfigurations/CatalogItemEntityTypeConfiguration.cs b/HomeWork33/Catalog/Catalog.Host/Data/EntityConfigurations/CatalogItemEntityTypeConfiguration.cs
new file mode 100644
index 0000000..0fc6e91
--- /dev/null
+++ b/HomeWork33/Catalog/Catalog.Host/Data/EntityConfigurations/CatalogItemEntityTypeConfiguration.cs
@@ -0,0 +1,36 @@
+using Catalog.Host.Data.Entities;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace Catalog.Host.Data.EntityConfigurations;
+
+public class CatalogItemEntityTypeConfiguration
+ : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder builder)
+ {
+ builder.ToTable("Catalog");
+
+ builder.Property(ci => ci.Id)
+ .UseHiLo("catalog_hilo")
+ .IsRequired();
+
+ builder.Property(ci => ci.Name)
+ .IsRequired(true)
+ .HasMaxLength(50);
+
+ builder.Property(ci => ci.Price)
+ .IsRequired(true);
+
+ builder.Property(ci => ci.PictureFileName)
+ .IsRequired(false);
+
+ builder.HasOne(ci => ci.CatalogBrand)
+ .WithMany()
+ .HasForeignKey(ci => ci.CatalogBrandId);
+
+ builder.HasOne(ci => ci.CatalogType)
+ .WithMany()
+ .HasForeignKey(ci => ci.CatalogTypeId);
+ }
+}
\ No newline at end of file
diff --git a/HomeWork33/Catalog/Catalog.Host/Data/EntityConfigurations/CatalogTypeEntityTypeConfiguration.cs b/HomeWork33/Catalog/Catalog.Host/Data/EntityConfigurations/CatalogTypeEntityTypeConfiguration.cs
new file mode 100644
index 0000000..1b92189
--- /dev/null
+++ b/HomeWork33/Catalog/Catalog.Host/Data/EntityConfigurations/CatalogTypeEntityTypeConfiguration.cs
@@ -0,0 +1,25 @@
+using Catalog.Host.Data.Entities;
+using Catalog.Host.Models.Dtos;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace Catalog.Host.Data.EntityConfigurations;
+
+public class CatalogTypeEntityTypeConfiguration
+ : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder builder)
+ {
+ builder.ToTable("CatalogType");
+
+ builder.HasKey(ci => ci.Id);
+
+ builder.Property(ci => ci.Id)
+ .UseHiLo("catalog_type_hilo")
+ .IsRequired();
+
+ builder.Property(cb => cb.Type)
+ .IsRequired()
+ .HasMaxLength(100);
+ }
+}
\ No newline at end of file
diff --git a/HomeWork33/Catalog/Catalog.Host/Data/PaginatedItems.cs b/HomeWork33/Catalog/Catalog.Host/Data/PaginatedItems.cs
new file mode 100644
index 0000000..130e77c
--- /dev/null
+++ b/HomeWork33/Catalog/Catalog.Host/Data/PaginatedItems.cs
@@ -0,0 +1,8 @@
+namespace Catalog.Host.Data;
+
+public class PaginatedItems
+{
+ public long TotalCount { get; init; }
+
+ public IEnumerable Data { get; init; } = null!;
+}
\ No newline at end of file
diff --git a/HomeWork33/Catalog/Catalog.Host/Dockerfile b/HomeWork33/Catalog/Catalog.Host/Dockerfile
new file mode 100644
index 0000000..9e09d15
--- /dev/null
+++ b/HomeWork33/Catalog/Catalog.Host/Dockerfile
@@ -0,0 +1,14 @@
+# https://hub.docker.com/_/microsoft-dotnet
+FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
+COPY . /src
+WORKDIR /src/Catalog/Catalog.Host
+RUN dotnet publish -c Release -o /app
+
+
+# final stage/image
+FROM mcr.microsoft.com/dotnet/aspnet:8.0
+WORKDIR /app
+COPY --from=build /app ./
+ENTRYPOINT ["dotnet", "Catalog.Host.dll"]
+
+
diff --git a/HomeWork33/Catalog/Catalog.Host/GlobalUsing.cs b/HomeWork33/Catalog/Catalog.Host/GlobalUsing.cs
new file mode 100644
index 0000000..f850a84
--- /dev/null
+++ b/HomeWork33/Catalog/Catalog.Host/GlobalUsing.cs
@@ -0,0 +1,12 @@
+global using System;
+global using System.Net;
+global using System.Threading.Tasks;
+global using Microsoft.Extensions.Logging;
+global using Microsoft.EntityFrameworkCore;
+global using Infrastructure.Services.Interfaces;
+global using Infrastructure.Services;
+global using Infrastructure;
+global using Microsoft.AspNetCore.Mvc;
+global using Microsoft.EntityFrameworkCore.Metadata.Builders;
+global using AutoMapper;
+global using Microsoft.Extensions.Options;
\ No newline at end of file
diff --git a/HomeWork33/Catalog/Catalog.Host/Mapping/CatalogItemPictureResolver.cs b/HomeWork33/Catalog/Catalog.Host/Mapping/CatalogItemPictureResolver.cs
new file mode 100644
index 0000000..6bbc5bb
--- /dev/null
+++ b/HomeWork33/Catalog/Catalog.Host/Mapping/CatalogItemPictureResolver.cs
@@ -0,0 +1,27 @@
+using AutoMapper;
+using Catalog.Host.Configurations;
+using Catalog.Host.Data.Entities;
+using Catalog.Host.Models.Dtos;
+using Microsoft.Extensions.Options;
+
+namespace Catalog.Host.Mapping;
+
+public class CatalogItemPictureResolver : IMemberValueResolver
+{
+ private readonly CatalogConfig _config;
+
+ public CatalogItemPictureResolver(IOptionsSnapshot config)
+ {
+ _config = config.Value;
+ }
+
+ public object Resolve(
+ CatalogItem source,
+ CatalogItemDto destination,
+ string sourceMember,
+ object destMember,
+ ResolutionContext context)
+ {
+ return $"{_config.Host}/{_config.ImgUrl}/{sourceMember}";
+ }
+}
\ No newline at end of file
diff --git a/HomeWork33/Catalog/Catalog.Host/Mapping/MappingProfile.cs b/HomeWork33/Catalog/Catalog.Host/Mapping/MappingProfile.cs
new file mode 100644
index 0000000..0adc55b
--- /dev/null
+++ b/HomeWork33/Catalog/Catalog.Host/Mapping/MappingProfile.cs
@@ -0,0 +1,20 @@
+using AutoMapper;
+using Catalog.Host.Data.Entities;
+using Catalog.Host.Models.Dtos;
+
+namespace Catalog.Host.Mapping;
+
+public class MappingProfile : Profile
+{
+ public MappingProfile()
+ {
+ CreateMap()
+ .ForMember("PictureUrl", opt
+ => opt.MapFrom(c => c.PictureFileName))
+ .ReverseMap();
+
+ CreateMap().ReverseMap();
+ CreateMap().ReverseMap();
+ }
+}
\ No newline at end of file
diff --git a/HomeWork33/Catalog/Catalog.Host/Migrations/20220108225624_InitialMigration.Designer.cs b/HomeWork33/Catalog/Catalog.Host/Migrations/20220108225624_InitialMigration.Designer.cs
new file mode 100644
index 0000000..bcd1d1f
--- /dev/null
+++ b/HomeWork33/Catalog/Catalog.Host/Migrations/20220108225624_InitialMigration.Designer.cs
@@ -0,0 +1,133 @@
+//
+using Catalog.Host.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace Catalog.Host.Migrations
+{
+ [DbContext(typeof(ApplicationDbContext))]
+ [Migration("20220108225624_InitialMigration")]
+ partial class InitialMigration
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "6.0.1")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.HasSequence("catalog_brand_hilo")
+ .IncrementsBy(10);
+
+ modelBuilder.HasSequence("catalog_hilo")
+ .IncrementsBy(10);
+
+ modelBuilder.HasSequence("catalog_type_hilo")
+ .IncrementsBy(10);
+
+ modelBuilder.Entity("Catalog.Host.Data.Entities.CatalogItem", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseHiLo(b.Property("Id"), "catalog_hilo");
+
+ b.Property("AvailableStock")
+ .HasColumnType("integer");
+
+ b.Property("CatalogBrandId")
+ .HasColumnType("integer");
+
+ b.Property("CatalogTypeId")
+ .HasColumnType("integer");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("character varying(50)");
+
+ b.Property("PictureFileName")
+ .HasColumnType("text");
+
+ b.Property("Price")
+ .HasColumnType("numeric");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CatalogBrandId");
+
+ b.HasIndex("CatalogTypeId");
+
+ b.ToTable("Catalog", (string)null);
+ });
+
+ modelBuilder.Entity("Catalog.Host.Data.Enums.CatalogBrand", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseHiLo(b.Property("Id"), "catalog_brand_hilo");
+
+ b.Property("Brand")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("character varying(100)");
+
+ b.HasKey("Id");
+
+ b.ToTable("CatalogBrand", (string)null);
+ });
+
+ modelBuilder.Entity("Catalog.Host.Data.Enums.CatalogType", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseHiLo(b.Property("Id"), "catalog_type_hilo");
+
+ b.Property("Type")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("character varying(100)");
+
+ b.HasKey("Id");
+
+ b.ToTable("CatalogType", (string)null);
+ });
+
+ modelBuilder.Entity("Catalog.Host.Data.Entities.CatalogItem", b =>
+ {
+ b.HasOne("Catalog.Host.Data.Enums.CatalogBrand", "CatalogBrand")
+ .WithMany()
+ .HasForeignKey("CatalogBrandId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Catalog.Host.Data.Enums.CatalogType", "CatalogType")
+ .WithMany()
+ .HasForeignKey("CatalogTypeId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("CatalogBrand");
+
+ b.Navigation("CatalogType");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/HomeWork33/Catalog/Catalog.Host/Migrations/20220108225624_InitialMigration.cs b/HomeWork33/Catalog/Catalog.Host/Migrations/20220108225624_InitialMigration.cs
new file mode 100644
index 0000000..df8f05e
--- /dev/null
+++ b/HomeWork33/Catalog/Catalog.Host/Migrations/20220108225624_InitialMigration.cs
@@ -0,0 +1,109 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Catalog.Host.Migrations
+{
+ public partial class InitialMigration : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateSequence(
+ name: "catalog_brand_hilo",
+ incrementBy: 10);
+
+ migrationBuilder.CreateSequence(
+ name: "catalog_hilo",
+ incrementBy: 10);
+
+ migrationBuilder.CreateSequence(
+ name: "catalog_type_hilo",
+ incrementBy: 10);
+
+ migrationBuilder.CreateTable(
+ name: "CatalogBrand",
+ columns: table => new
+ {
+ Id = table.Column(type: "integer", nullable: false),
+ Brand = table.Column(type: "character varying(100)", maxLength: 100, nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_CatalogBrand", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "CatalogType",
+ columns: table => new
+ {
+ Id = table.Column(type: "integer", nullable: false),
+ Type = table.Column(type: "character varying(100)", maxLength: 100, nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_CatalogType", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "Catalog",
+ columns: table => new
+ {
+ Id = table.Column(type: "integer", nullable: false),
+ Name = table.Column(type: "character varying(50)", maxLength: 50, nullable: false),
+ Description = table.Column(type: "text", nullable: false),
+ Price = table.Column(type: "numeric", nullable: false),
+ PictureFileName = table.Column(type: "text", nullable: true),
+ CatalogTypeId = table.Column(type: "integer", nullable: false),
+ CatalogBrandId = table.Column(type: "integer", nullable: false),
+ AvailableStock = table.Column(type: "integer", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Catalog", x => x.Id);
+ table.ForeignKey(
+ name: "FK_Catalog_CatalogBrand_CatalogBrandId",
+ column: x => x.CatalogBrandId,
+ principalTable: "CatalogBrand",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ table.ForeignKey(
+ name: "FK_Catalog_CatalogType_CatalogTypeId",
+ column: x => x.CatalogTypeId,
+ principalTable: "CatalogType",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_Catalog_CatalogBrandId",
+ table: "Catalog",
+ column: "CatalogBrandId");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_Catalog_CatalogTypeId",
+ table: "Catalog",
+ column: "CatalogTypeId");
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "Catalog");
+
+ migrationBuilder.DropTable(
+ name: "CatalogBrand");
+
+ migrationBuilder.DropTable(
+ name: "CatalogType");
+
+ migrationBuilder.DropSequence(
+ name: "catalog_brand_hilo");
+
+ migrationBuilder.DropSequence(
+ name: "catalog_hilo");
+
+ migrationBuilder.DropSequence(
+ name: "catalog_type_hilo");
+ }
+ }
+}
diff --git a/HomeWork33/Catalog/Catalog.Host/Migrations/ApplicationDbContextModelSnapshot.cs b/HomeWork33/Catalog/Catalog.Host/Migrations/ApplicationDbContextModelSnapshot.cs
new file mode 100644
index 0000000..7298040
--- /dev/null
+++ b/HomeWork33/Catalog/Catalog.Host/Migrations/ApplicationDbContextModelSnapshot.cs
@@ -0,0 +1,131 @@
+//
+using Catalog.Host.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace Catalog.Host.Migrations
+{
+ [DbContext(typeof(ApplicationDbContext))]
+ partial class ApplicationDbContextModelSnapshot : ModelSnapshot
+ {
+ protected override void BuildModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "6.0.1")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.HasSequence("catalog_brand_hilo")
+ .IncrementsBy(10);
+
+ modelBuilder.HasSequence("catalog_hilo")
+ .IncrementsBy(10);
+
+ modelBuilder.HasSequence("catalog_type_hilo")
+ .IncrementsBy(10);
+
+ modelBuilder.Entity("Catalog.Host.Data.Entities.CatalogItem", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseHiLo(b.Property("Id"), "catalog_hilo");
+
+ b.Property("AvailableStock")
+ .HasColumnType("integer");
+
+ b.Property("CatalogBrandId")
+ .HasColumnType("integer");
+
+ b.Property("CatalogTypeId")
+ .HasColumnType("integer");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("character varying(50)");
+
+ b.Property("PictureFileName")
+ .HasColumnType("text");
+
+ b.Property("Price")
+ .HasColumnType("numeric");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CatalogBrandId");
+
+ b.HasIndex("CatalogTypeId");
+
+ b.ToTable("Catalog", (string)null);
+ });
+
+ modelBuilder.Entity("Catalog.Host.Data.Enums.CatalogBrand", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseHiLo(b.Property("Id"), "catalog_brand_hilo");
+
+ b.Property("Brand")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("character varying(100)");
+
+ b.HasKey("Id");
+
+ b.ToTable("CatalogBrand", (string)null);
+ });
+
+ modelBuilder.Entity("Catalog.Host.Data.Enums.CatalogType", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseHiLo(b.Property("Id"), "catalog_type_hilo");
+
+ b.Property("Type")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("character varying(100)");
+
+ b.HasKey("Id");
+
+ b.ToTable("CatalogType", (string)null);
+ });
+
+ modelBuilder.Entity("Catalog.Host.Data.Entities.CatalogItem", b =>
+ {
+ b.HasOne("Catalog.Host.Data.Enums.CatalogBrand", "CatalogBrand")
+ .WithMany()
+ .HasForeignKey("CatalogBrandId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Catalog.Host.Data.Enums.CatalogType", "CatalogType")
+ .WithMany()
+ .HasForeignKey("CatalogTypeId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("CatalogBrand");
+
+ b.Navigation("CatalogType");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/HomeWork33/Catalog/Catalog.Host/Models/Dtos/CatalogBrandDto.cs b/HomeWork33/Catalog/Catalog.Host/Models/Dtos/CatalogBrandDto.cs
new file mode 100644
index 0000000..8e4ad8f
--- /dev/null
+++ b/HomeWork33/Catalog/Catalog.Host/Models/Dtos/CatalogBrandDto.cs
@@ -0,0 +1,9 @@
+#pragma warning disable CS8618
+namespace Catalog.Host.Models.Dtos;
+
+public class CatalogBrandDto
+{
+ public int Id { get; set; }
+
+ public string Brand { get; set; }
+}
\ No newline at end of file
diff --git a/HomeWork33/Catalog/Catalog.Host/Models/Dtos/CatalogItemDto.cs b/HomeWork33/Catalog/Catalog.Host/Models/Dtos/CatalogItemDto.cs
new file mode 100644
index 0000000..b8f4d3d
--- /dev/null
+++ b/HomeWork33/Catalog/Catalog.Host/Models/Dtos/CatalogItemDto.cs
@@ -0,0 +1,21 @@
+#pragma warning disable CS8618
+namespace Catalog.Host.Models.Dtos;
+
+public class CatalogItemDto
+{
+ public int Id { get; set; }
+
+ public string Name { get; set; }
+
+ public string Description { get; set; }
+
+ public decimal Price { get; set; }
+
+ public string PictureUrl { get; set; }
+
+ public CatalogTypeDto CatalogType { get; set; }
+
+ public CatalogBrandDto CatalogBrand { get; set; }
+
+ public int AvailableStock { get; set; }
+}
diff --git a/HomeWork33/Catalog/Catalog.Host/Models/Dtos/CatalogTypeDto.cs b/HomeWork33/Catalog/Catalog.Host/Models/Dtos/CatalogTypeDto.cs
new file mode 100644
index 0000000..4e1d7b7
--- /dev/null
+++ b/HomeWork33/Catalog/Catalog.Host/Models/Dtos/CatalogTypeDto.cs
@@ -0,0 +1,9 @@
+#pragma warning disable CS8618
+namespace Catalog.Host.Models.Dtos;
+
+public class CatalogTypeDto
+{
+ public int Id { get; set; }
+
+ public string Type { get; set; }
+}
\ No newline at end of file
diff --git a/HomeWork33/Catalog/Catalog.Host/Models/Requests/CreateProductRequest.cs b/HomeWork33/Catalog/Catalog.Host/Models/Requests/CreateProductRequest.cs
new file mode 100644
index 0000000..20de15b
--- /dev/null
+++ b/HomeWork33/Catalog/Catalog.Host/Models/Requests/CreateProductRequest.cs
@@ -0,0 +1,20 @@
+using Catalog.Host.Data.Entities;
+
+namespace Catalog.Host.Models.Requests;
+
+public class CreateProductRequest
+{
+ public string Name { get; set; } = null!;
+
+ public string Description { get; set; } = null!;
+
+ public decimal Price { get; set; }
+
+ public string PictureFileName { get; set; } = null!;
+
+ public int CatalogTypeId { get; set; }
+
+ public int CatalogBrandId { get; set; }
+
+ public int AvailableStock { get; set; }
+}
\ No newline at end of file
diff --git a/HomeWork33/Catalog/Catalog.Host/Models/Requests/PaginatedItemsRequest.cs b/HomeWork33/Catalog/Catalog.Host/Models/Requests/PaginatedItemsRequest.cs
new file mode 100644
index 0000000..f296711
--- /dev/null
+++ b/HomeWork33/Catalog/Catalog.Host/Models/Requests/PaginatedItemsRequest.cs
@@ -0,0 +1,8 @@
+namespace Catalog.Host.Models.Requests;
+
+public class PaginatedItemsRequest
+{
+ public int PageIndex { get; set; }
+
+ public int PageSize { get; set; }
+}
\ No newline at end of file
diff --git a/HomeWork33/Catalog/Catalog.Host/Models/Response/AddItemResponse.cs b/HomeWork33/Catalog/Catalog.Host/Models/Response/AddItemResponse.cs
new file mode 100644
index 0000000..9e91f5a
--- /dev/null
+++ b/HomeWork33/Catalog/Catalog.Host/Models/Response/AddItemResponse.cs
@@ -0,0 +1,6 @@
+namespace Catalog.Host.Models.Response;
+
+public class AddItemResponse
+{
+ public T Id { get; set; } = default(T) !;
+}
\ No newline at end of file
diff --git a/HomeWork33/Catalog/Catalog.Host/Models/Response/PaginatedItemsResponse.cs b/HomeWork33/Catalog/Catalog.Host/Models/Response/PaginatedItemsResponse.cs
new file mode 100644
index 0000000..97683a2
--- /dev/null
+++ b/HomeWork33/Catalog/Catalog.Host/Models/Response/PaginatedItemsResponse.cs
@@ -0,0 +1,12 @@
+namespace Catalog.Host.Models.Response;
+
+public class PaginatedItemsResponse
+{
+ public int PageIndex { get; init; }
+
+ public int PageSize { get; init; }
+
+ public long Count { get; init; }
+
+ public IEnumerable Data { get; init; } = null!;
+}
diff --git a/HomeWork33/Catalog/Catalog.Host/Program.cs b/HomeWork33/Catalog/Catalog.Host/Program.cs
new file mode 100644
index 0000000..2498c0b
--- /dev/null
+++ b/HomeWork33/Catalog/Catalog.Host/Program.cs
@@ -0,0 +1,70 @@
+using Catalog.Host.Configurations;
+using Catalog.Host.Data;
+using Catalog.Host.Repositories;
+using Catalog.Host.Repositories.Interfaces;
+using Catalog.Host.Services;
+using Catalog.Host.Services.Interfaces;
+using Microsoft.EntityFrameworkCore;
+
+var configuration = GetConfiguration();
+
+var builder = WebApplication.CreateBuilder(args);
+builder.Services.AddControllers();
+builder.Services.Configure(configuration);
+builder.Services.AddSwaggerGen();
+builder.Services.AddAutoMapper(typeof(Program));
+
+builder.Services.AddTransient();
+builder.Services.AddTransient();
+builder.Services.AddTransient();
+builder.Services.AddTransient();
+builder.Services.AddTransient();
+builder.Services.AddTransient();
+builder.Services.AddTransient();
+
+builder.Services.AddDbContextFactory(opts => opts.UseNpgsql(configuration["ConnectionString"]!));
+builder.Services.AddScoped, DbContextWrapper>();
+
+var app = builder.Build();
+
+app.UseSwagger();
+app.UseSwaggerUI();
+app.UseRouting();
+
+app.UseEndpoints(endpoints =>
+{
+ endpoints.MapDefaultControllerRoute();
+ endpoints.MapControllers();
+});
+
+CreateDbIfNotExists(app);
+app.Run();
+
+IConfiguration GetConfiguration()
+{
+ var builder = new ConfigurationBuilder()
+ .SetBasePath(Directory.GetCurrentDirectory())
+ .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
+ .AddEnvironmentVariables();
+
+ return builder.Build();
+}
+
+void CreateDbIfNotExists(IHost host)
+{
+ using (var scope = host.Services.CreateScope())
+ {
+ var services = scope.ServiceProvider;
+ try
+ {
+ var context = services.GetRequiredService();
+
+ DbInitializer.Initialize(context).Wait();
+ }
+ catch (Exception ex)
+ {
+ var logger = services.GetRequiredService>();
+ logger.LogError(ex, "An error occurred creating the DB.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/HomeWork33/Catalog/Catalog.Host/Repositories/CatalogBrandRepository.cs b/HomeWork33/Catalog/Catalog.Host/Repositories/CatalogBrandRepository.cs
new file mode 100644
index 0000000..55d66e3
--- /dev/null
+++ b/HomeWork33/Catalog/Catalog.Host/Repositories/CatalogBrandRepository.cs
@@ -0,0 +1,82 @@
+using Catalog.Host.Data;
+using Catalog.Host.Data.Entities;
+using Catalog.Host.Repositories.Interfaces;
+
+namespace Catalog.Host.Repositories
+{
+ public class CatalogBrandRepository : ICatalogBrandRepository
+ {
+ private readonly ApplicationDbContext _dbContext;
+ private readonly ILogger _logger;
+
+ public CatalogBrandRepository(IDbContextWrapper contextWrapper, ILogger logger)
+ {
+ _dbContext = contextWrapper.DbContext;
+ _logger = logger;
+ }
+
+ public async Task GetById(int? id)
+ {
+ if (id is null)
+ {
+ _logger.LogWarning("id null!");
+ return null;
+ }
+
+ return await _dbContext.CatalogBrands
+ .FirstOrDefaultAsync(f => f.Id == id);
+ }
+
+ public async Task AddAsync(string? brand)
+ {
+ if(brand is null)
+ {
+ _logger.LogWarning("brand null!");
+ return null;
+ }
+
+ var entity = await _dbContext.CatalogBrands
+
+ .AddAsync(new CatalogBrand()
+ {
+ Brand = brand
+ });
+
+ await _dbContext.SaveChangesAsync();
+
+ return entity.Entity.Id;
+ }
+
+ public async Task DeleteAsync(int? id)
+ {
+ if (id is null)
+ {
+ _logger.LogWarning("Id can`t be null");
+ return "Id can`t be null";
+ }
+
+ var entity = await GetById(id);
+ var message = _dbContext.CatalogBrands.Remove(entity!);
+
+ await _dbContext.SaveChangesAsync();
+
+ return message.State.ToString();
+ }
+
+ public async Task UpdateAsync(CatalogBrand? catalogBrand)
+ {
+ if(catalogBrand is null)
+ {
+ _logger.LogWarning("entity is null");
+ return null;
+ }
+
+ var entity = await GetById(catalogBrand.Id);
+ entity!.Brand = catalogBrand.Brand;
+
+ await _dbContext.SaveChangesAsync();
+
+ return entity;
+ }
+ }
+}
diff --git a/HomeWork33/Catalog/Catalog.Host/Repositories/CatalogItemRepository.cs b/HomeWork33/Catalog/Catalog.Host/Repositories/CatalogItemRepository.cs
new file mode 100644
index 0000000..50f0ce8
--- /dev/null
+++ b/HomeWork33/Catalog/Catalog.Host/Repositories/CatalogItemRepository.cs
@@ -0,0 +1,118 @@
+using Catalog.Host.Data;
+using Catalog.Host.Data.Entities;
+using Catalog.Host.Repositories.Interfaces;
+
+namespace Catalog.Host.Repositories;
+
+public class CatalogItemRepository : ICatalogItemRepository
+{
+ private readonly ApplicationDbContext _dbContext;
+ private readonly ILogger _logger;
+
+ public CatalogItemRepository(
+ IDbContextWrapper dbContextWrapper,
+ ILogger logger)
+ {
+ _dbContext = dbContextWrapper.DbContext;
+ _logger = logger;
+ }
+
+ public async Task> GetByPageAsync(int pageIndex, int pageSize)
+ {
+ var totalItems = await _dbContext.CatalogItems
+ .LongCountAsync();
+
+ var itemsOnPage = await _dbContext.CatalogItems
+ .Include(i => i.CatalogBrand)
+ .Include(i => i.CatalogType)
+ .OrderBy(c => c.Name)
+ .Skip(pageSize * pageIndex)
+ .Take(pageSize)
+ .ToListAsync();
+
+ return new PaginatedItems() { TotalCount = totalItems, Data = itemsOnPage };
+ }
+
+ public async Task Add(
+ string name,
+ string description,
+ decimal price,
+ int availableStock,
+ int catalogBrandId,
+ int catalogTypeId,
+ string pictureFileName)
+ {
+ var item = await _dbContext.AddAsync(new CatalogItem
+ {
+ CatalogBrandId = catalogBrandId,
+ CatalogTypeId = catalogTypeId,
+ Description = description,
+ Name = name,
+ PictureFileName = pictureFileName,
+ Price = price
+ });
+
+ await _dbContext.SaveChangesAsync();
+
+ return item.Entity.Id;
+ }
+
+ public async Task GetCatalogItemsByIdAsync(int? idItem)
+ {
+ return await _dbContext.CatalogItems
+ .Include(i => i.CatalogType)
+ .Include(i=>i.CatalogBrand)
+ .FirstOrDefaultAsync(item => item.Id == idItem);
+ }
+
+ public async Task> GetCatalogItemsByBrandAsync(int? idBrand)
+ {
+ return await _dbContext.CatalogItems
+ .Include(i=>i.CatalogType)
+ .Include(i => i.CatalogBrand)
+ .Select(item => item)
+ .Where(item => item.CatalogBrand.Id == idBrand)
+ .ToListAsync();
+ }
+
+ public async Task> GetCatalogItemsByTypeAsync(int? idType)
+ {
+ return await _dbContext.CatalogItems
+ .Include(i => i.CatalogBrand)
+ .Include(i => i.CatalogType)
+ .Select(item => item)
+ .Where(item => item.CatalogType.Id == idType)
+ .ToListAsync();
+ }
+
+ public async Task DeleteAsync(int? id)
+ {
+ if(id is null)
+ {
+ return "Id can`t be null";
+ }
+ var item = await GetCatalogItemsByIdAsync(id);
+ var message = _dbContext.CatalogItems.Remove(item!);
+ await _dbContext.SaveChangesAsync();
+ return message.State.ToString();
+ }
+
+ public async Task Update(CatalogItem catalogItem)
+ {
+ var item = await GetCatalogItemsByIdAsync(catalogItem.Id);
+
+ item!.Price = catalogItem.Price;
+ item.Description = catalogItem.Description;
+ item.PictureFileName = catalogItem.PictureFileName;
+ item.Name = catalogItem.Name;
+ item.AvailableStock = catalogItem.AvailableStock;
+ item.CatalogBrandId = catalogItem.CatalogBrandId;
+ item.CatalogType = catalogItem.CatalogType;
+ item.CatalogTypeId = catalogItem.CatalogTypeId;
+ item.CatalogBrand = catalogItem.CatalogBrand;
+
+ await _dbContext.SaveChangesAsync();
+
+ return item;
+ }
+}
\ No newline at end of file
diff --git a/HomeWork33/Catalog/Catalog.Host/Repositories/CatalogTypeRepository.cs b/HomeWork33/Catalog/Catalog.Host/Repositories/CatalogTypeRepository.cs
new file mode 100644
index 0000000..dc46642
--- /dev/null
+++ b/HomeWork33/Catalog/Catalog.Host/Repositories/CatalogTypeRepository.cs
@@ -0,0 +1,66 @@
+using Catalog.Host.Data;
+using Catalog.Host.Data.Entities;
+using Catalog.Host.Models.Dtos;
+using Catalog.Host.Repositories.Interfaces;
+using Catalog.Host.Services.Interfaces;
+using Microsoft.EntityFrameworkCore;
+
+namespace Catalog.Host.Repositories
+{
+ public class CatalogTypeRepository : ICatalogTypeRepository
+ {
+ private readonly ApplicationDbContext _dbContext;
+ private readonly ILogger _logger;
+
+ public CatalogTypeRepository(
+ IDbContextWrapper dbContextWrapper,
+ ILogger logger)
+ {
+ _dbContext = dbContextWrapper.DbContext;
+ _logger = logger;
+ }
+
+ public async Task GetById(int? id)
+ {
+ return await _dbContext.CatalogTypes
+ .FirstOrDefaultAsync(f => f.Id == id);
+ }
+
+ public async Task AddTypeAsync(string? type)
+ {
+ if(type is null)
+ {
+ return null;
+ }
+
+ var entity = await _dbContext.CatalogTypes.AddAsync(new CatalogType()
+ {
+ Type = type
+ });
+
+ await _dbContext.SaveChangesAsync();
+
+ return entity.Entity.Id;
+ }
+
+ public async Task