summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Chebotar <s.chebotar@gmail.com>2022-12-14 09:53:10 +0300
committerSergey Chebotar <s.chebotar@gmail.com>2022-12-14 09:53:10 +0300
commitda94ed6d89ac2b933718419bbe42f2d913514231 (patch)
tree4c3f9ba7f5c54c564ee28b0e3c973c374f2499ac
Init commit
-rw-r--r--.gitignore454
-rw-r--r--.vscode/launch.json40
-rw-r--r--.vscode/tasks.json41
-rw-r--r--RhSolutions.Api.Tests/RhSolutions.Api.Tests.csproj28
-rw-r--r--RhSolutions.Api.Tests/SkuExtensionsTest.cs25
-rw-r--r--RhSolutions.Api.Tests/Usings.cs1
-rw-r--r--RhSolutions.Api/Controllers/ProductsController.cs73
-rw-r--r--RhSolutions.Api/Controllers/SearchController.cs28
-rw-r--r--RhSolutions.Api/Dockerfile13
-rw-r--r--RhSolutions.Api/Migrations/20221201071323_Init.Designer.cs70
-rw-r--r--RhSolutions.Api/Migrations/20221201071323_Init.cs43
-rw-r--r--RhSolutions.Api/Migrations/RhSolutionsContextModelSnapshot.cs67
-rw-r--r--RhSolutions.Api/Models/Measure.cs4
-rw-r--r--RhSolutions.Api/Models/Product.cs34
-rw-r--r--RhSolutions.Api/Models/RhsolutionsContext.cs11
-rw-r--r--RhSolutions.Api/Models/Sku.cs127
-rw-r--r--RhSolutions.Api/Program.cs34
-rw-r--r--RhSolutions.Api/Properties/launchSettings.json29
-rw-r--r--RhSolutions.Api/RhSolutions.Api.csproj20
-rw-r--r--RhSolutions.Api/Services/ClosedXMLParser.cs159
-rw-r--r--RhSolutions.Api/Services/IPricelistParser.cs9
-rw-r--r--RhSolutions.Api/appsettings.Development.json9
-rw-r--r--RhSolutions.Api/appsettings.json10
-rw-r--r--RhSolutions.Api/global.json5
-rw-r--r--RhSolutions.Deploy/database/Dockerfile6
-rw-r--r--RhSolutions.Deploy/database/init-database.sql25
-rw-r--r--RhSolutions.Deploy/docker-compose.yml31
-rw-r--r--RhSolutions.sln28
28 files changed, 1424 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2afa2e2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,454 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# Tye
+.tye/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+##
+## Visual studio for Mac
+##
+
+
+# globs
+Makefile.in
+*.userprefs
+*.usertasks
+config.make
+config.status
+aclocal.m4
+install-sh
+autom4te.cache/
+*.tar.gz
+tarballs/
+test-results/
+
+# Mac bundle stuff
+*.dmg
+*.app
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
+# Windows thumbnail cache files
+Thumbs.db
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+##
+## Visual Studio Code
+##
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..ee1dc7e
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,40 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ // Use IntelliSense to find out which attributes exist for C# debugging
+ // Use hover for the description of the existing attributes
+ // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
+ "name": ".NET Core Launch (web)",
+ "type": "coreclr",
+ "request": "launch",
+ "preLaunchTask": "build",
+ // If you have changed target frameworks, make sure to update the program path.
+ "program": "${workspaceFolder}/RhSolutions.Api/bin/Debug/net6.0/RhSolutions.Api.dll",
+ "args": [],
+ "cwd": "${workspaceFolder}/RhSolutions.Api",
+ "stopAtEntry": false,
+ // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser
+ // "serverReadyAction": {
+ // "action": "openExternally",
+ // "pattern": "\\bNow listening on:\\s+(https?://\\S+)"
+ // },
+ "env": {
+ "ASPNETCORE_ENVIRONMENT": "Development",
+ "DB_HOST": "localhost",
+ "DB_PORT": "5432",
+ "DB_USER": "user",
+ "DB_PASSWORD": "password",
+ "DB_DATABASE": "database"
+ },
+ "sourceFileMap": {
+ "/Views": "${workspaceFolder}/Views"
+ }
+ },
+ {
+ "name": ".NET Core Attach",
+ "type": "coreclr",
+ "request": "attach"
+ }
+ ]
+} \ No newline at end of file
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 0000000..de9c113
--- /dev/null
+++ b/.vscode/tasks.json
@@ -0,0 +1,41 @@
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "build",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "build",
+ "${workspaceFolder}/RhSolutions.Api/RhSolutions.Api.csproj",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary"
+ ],
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "publish",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "publish",
+ "${workspaceFolder}/RhSolutions.Api/RhSolutions.Api.csproj",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary"
+ ],
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "watch",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "watch",
+ "run",
+ "--project",
+ "${workspaceFolder}/RhSolutions.Api/RhSolutions.Api.csproj"
+ ],
+ "problemMatcher": "$msCompile"
+ }
+ ]
+} \ No newline at end of file
diff --git a/RhSolutions.Api.Tests/RhSolutions.Api.Tests.csproj b/RhSolutions.Api.Tests/RhSolutions.Api.Tests.csproj
new file mode 100644
index 0000000..263aa54
--- /dev/null
+++ b/RhSolutions.Api.Tests/RhSolutions.Api.Tests.csproj
@@ -0,0 +1,28 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>net6.0</TargetFramework>
+ <ImplicitUsings>enable</ImplicitUsings>
+ <Nullable>enable</Nullable>
+
+ <IsPackable>false</IsPackable>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
+ <PackageReference Include="xunit" Version="2.4.1" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ <PrivateAssets>all</PrivateAssets>
+ </PackageReference>
+ <PackageReference Include="coverlet.collector" Version="3.1.2">
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ <PrivateAssets>all</PrivateAssets>
+ </PackageReference>
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\RhSolutions.Api\RhSolutions.Api.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git a/RhSolutions.Api.Tests/SkuExtensionsTest.cs b/RhSolutions.Api.Tests/SkuExtensionsTest.cs
new file mode 100644
index 0000000..f260af7
--- /dev/null
+++ b/RhSolutions.Api.Tests/SkuExtensionsTest.cs
@@ -0,0 +1,25 @@
+using RhSolutions.Api.Models;
+
+namespace RhSolutions.Tests
+{
+ public class SkuExtensionsTests
+ {
+ [Theory]
+ [InlineData("11600011001")]
+ [InlineData(" 11600011001")]
+ [InlineData("11600011001 ")]
+ [InlineData("string 11600011001")]
+ [InlineData("11600011001 string")]
+ [InlineData("160001-001")]
+ [InlineData("string 160001-001")]
+ [InlineData("160001-001 string")]
+ [InlineData("160001001")]
+ [InlineData("string 160001001")]
+ [InlineData("160001001 string")]
+ public void TestName(string input)
+ {
+ Sku.TryParse(input, out IEnumerable<Sku> sku);
+ Assert.Equal(new Sku("160001", "001"), sku.FirstOrDefault());
+ }
+ }
+} \ No newline at end of file
diff --git a/RhSolutions.Api.Tests/Usings.cs b/RhSolutions.Api.Tests/Usings.cs
new file mode 100644
index 0000000..8c927eb
--- /dev/null
+++ b/RhSolutions.Api.Tests/Usings.cs
@@ -0,0 +1 @@
+global using Xunit; \ No newline at end of file
diff --git a/RhSolutions.Api/Controllers/ProductsController.cs b/RhSolutions.Api/Controllers/ProductsController.cs
new file mode 100644
index 0000000..d7d1082
--- /dev/null
+++ b/RhSolutions.Api/Controllers/ProductsController.cs
@@ -0,0 +1,73 @@
+using Microsoft.AspNetCore.Mvc;
+using RhSolutions.Api.Models;
+using RhSolutions.Api.Services;
+
+namespace RhSolutions.Api.Controllers
+{
+ [Route("api/[controller]")]
+ public class ProductsController : ControllerBase
+ {
+ private RhSolutionsContext dbContext;
+ private IPricelistParser parser;
+
+ public ProductsController(RhSolutionsContext dbContext, IPricelistParser parser)
+ {
+ this.dbContext = dbContext;
+ this.parser = parser;
+ }
+
+ [HttpGet]
+ public IAsyncEnumerable<Product> GetProducts()
+ {
+ return dbContext.Products
+ .AsAsyncEnumerable();
+ }
+
+ [HttpGet("{id}")]
+ public IEnumerable<Product> GetProduct(string id)
+ {
+ return dbContext.Products
+ .Where(p => p.ProductSku!.Equals(id));
+ }
+
+ [HttpPost]
+ public async Task<IActionResult> PostProductsFromXls()
+ {
+ try
+ {
+ var products = parser.GetProducts(HttpContext);
+ await foreach (var p in products)
+ {
+ using (p)
+ {
+ dbContext.Add<Product>(p);
+ }
+ }
+
+ dbContext.SaveChanges();
+ return Ok(products);
+ }
+ catch (Exception ex)
+ {
+ return BadRequest(ex.Message);
+ }
+ }
+
+ [HttpDelete]
+ public IActionResult DeleteAllProducts()
+ {
+ List<Product> deleted = new();
+ if (dbContext.Products.Count() > 0)
+ {
+ foreach (Product p in dbContext.Products)
+ {
+ deleted.Add(p);
+ dbContext.Remove(p);
+ }
+ dbContext.SaveChanges();
+ return Ok(deleted);
+ }
+ else return Ok("Empty db");
+ }
+ }
+} \ No newline at end of file
diff --git a/RhSolutions.Api/Controllers/SearchController.cs b/RhSolutions.Api/Controllers/SearchController.cs
new file mode 100644
index 0000000..a2ed194
--- /dev/null
+++ b/RhSolutions.Api/Controllers/SearchController.cs
@@ -0,0 +1,28 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using RhSolutions.Api.Models;
+
+namespace RhSolutions.Api.Controllers
+{
+ [Route("api/[controller]")]
+ public class SearchController : ControllerBase
+ {
+ private RhSolutionsContext context;
+
+ public SearchController(RhSolutionsContext context)
+ {
+ this.context = context;
+ }
+
+ [HttpGet]
+ public IAsyncEnumerable<Product> SearchProducts([FromQuery] string query)
+ {
+ return context.Products
+ .Where(p => EF.Functions.ToTsVector(
+ "russian", string.Join(' ',
+ new [] {p.ProductLine ?? string.Empty, p.Name ?? string.Empty }))
+ .Matches(EF.Functions.WebSearchToTsQuery("russian", query)))
+ .AsAsyncEnumerable();
+ }
+ }
+} \ No newline at end of file
diff --git a/RhSolutions.Api/Dockerfile b/RhSolutions.Api/Dockerfile
new file mode 100644
index 0000000..93139c8
--- /dev/null
+++ b/RhSolutions.Api/Dockerfile
@@ -0,0 +1,13 @@
+FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
+WORKDIR /app
+
+COPY . ./
+RUN dotnet restore
+RUN dotnet publish -c Release -o out
+
+FROM mcr.microsoft.com/dotnet/aspnet:6.0
+EXPOSE 5000
+WORKDIR /app
+COPY --from=build /app/out .
+ENV ASPNETCORE_ENVIRONMENT Production
+ENTRYPOINT [ "dotnet", "RhSolutions.Api.dll", "--urls=http://0.0.0.0:5000" ] \ No newline at end of file
diff --git a/RhSolutions.Api/Migrations/20221201071323_Init.Designer.cs b/RhSolutions.Api/Migrations/20221201071323_Init.Designer.cs
new file mode 100644
index 0000000..c62450e
--- /dev/null
+++ b/RhSolutions.Api/Migrations/20221201071323_Init.Designer.cs
@@ -0,0 +1,70 @@
+// <auto-generated />
+using System;
+using System.Collections.Generic;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+using RhSolutions.Api.Models;
+
+#nullable disable
+
+namespace RhSolutions.Api.Migrations
+{
+ [DbContext(typeof(RhSolutionsContext))]
+ [Migration("20221201071323_Init")]
+ partial class Init
+ {
+ /// <inheritdoc />
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "7.0.0")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("RhSolutions.Api.Models.Product", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<double?>("DeliveryMakeUp")
+ .HasColumnType("double precision");
+
+ b.Property<List<string>>("DeprecatedSkus")
+ .IsRequired()
+ .HasColumnType("text[]");
+
+ b.Property<bool?>("IsOnWarehouse")
+ .HasColumnType("boolean");
+
+ b.Property<string>("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property<decimal>("Price")
+ .HasColumnType("decimal(8,2)");
+
+ b.Property<string>("ProductLine")
+ .HasColumnType("text");
+
+ b.Property<int>("ProductMeasure")
+ .HasColumnType("integer");
+
+ b.Property<string>("ProductSku")
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.ToTable("Products");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/RhSolutions.Api/Migrations/20221201071323_Init.cs b/RhSolutions.Api/Migrations/20221201071323_Init.cs
new file mode 100644
index 0000000..2ac6ad0
--- /dev/null
+++ b/RhSolutions.Api/Migrations/20221201071323_Init.cs
@@ -0,0 +1,43 @@
+using System.Collections.Generic;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace RhSolutions.Api.Migrations
+{
+ /// <inheritdoc />
+ public partial class Init : Migration
+ {
+ /// <inheritdoc />
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "Products",
+ columns: table => new
+ {
+ Id = table.Column<int>(type: "integer", nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
+ ProductSku = table.Column<string>(type: "text", nullable: true),
+ DeprecatedSkus = table.Column<List<string>>(type: "text[]", nullable: false),
+ Name = table.Column<string>(type: "text", nullable: false),
+ ProductLine = table.Column<string>(type: "text", nullable: true),
+ IsOnWarehouse = table.Column<bool>(type: "boolean", nullable: true),
+ ProductMeasure = table.Column<int>(type: "integer", nullable: false),
+ DeliveryMakeUp = table.Column<double>(type: "double precision", nullable: true),
+ Price = table.Column<decimal>(type: "numeric(8,2)", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Products", x => x.Id);
+ });
+ }
+
+ /// <inheritdoc />
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "Products");
+ }
+ }
+}
diff --git a/RhSolutions.Api/Migrations/RhSolutionsContextModelSnapshot.cs b/RhSolutions.Api/Migrations/RhSolutionsContextModelSnapshot.cs
new file mode 100644
index 0000000..60c89e0
--- /dev/null
+++ b/RhSolutions.Api/Migrations/RhSolutionsContextModelSnapshot.cs
@@ -0,0 +1,67 @@
+// <auto-generated />
+using System;
+using System.Collections.Generic;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+using RhSolutions.Api.Models;
+
+#nullable disable
+
+namespace RhSolutions.Api.Migrations
+{
+ [DbContext(typeof(RhSolutionsContext))]
+ partial class RhSolutionsContextModelSnapshot : ModelSnapshot
+ {
+ protected override void BuildModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "7.0.0")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("RhSolutions.Api.Models.Product", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<double?>("DeliveryMakeUp")
+ .HasColumnType("double precision");
+
+ b.Property<List<string>>("DeprecatedSkus")
+ .IsRequired()
+ .HasColumnType("text[]");
+
+ b.Property<bool?>("IsOnWarehouse")
+ .HasColumnType("boolean");
+
+ b.Property<string>("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property<decimal>("Price")
+ .HasColumnType("decimal(8,2)");
+
+ b.Property<string>("ProductLine")
+ .HasColumnType("text");
+
+ b.Property<int>("ProductMeasure")
+ .HasColumnType("integer");
+
+ b.Property<string>("ProductSku")
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.ToTable("Products");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/RhSolutions.Api/Models/Measure.cs b/RhSolutions.Api/Models/Measure.cs
new file mode 100644
index 0000000..9fbd20c
--- /dev/null
+++ b/RhSolutions.Api/Models/Measure.cs
@@ -0,0 +1,4 @@
+namespace RhSolutions.Api.Models
+{
+ public enum Measure { Kg, M, M2, P }
+} \ No newline at end of file
diff --git a/RhSolutions.Api/Models/Product.cs b/RhSolutions.Api/Models/Product.cs
new file mode 100644
index 0000000..a8d1ec5
--- /dev/null
+++ b/RhSolutions.Api/Models/Product.cs
@@ -0,0 +1,34 @@
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Diagnostics;
+using System.Text.Json.Serialization;
+
+namespace RhSolutions.Api.Models
+{
+ public partial class Product : IDisposable
+ {
+ [Key]
+ [JsonIgnore]
+ public int Id { get; set; }
+ public string ProductSku { get; set; } = string.Empty;
+ public List<string> DeprecatedSkus { get; set; } = new();
+ public string Name { get; set; } = string.Empty;
+ public string ProductLine { get; set; } = string.Empty;
+ public bool? IsOnWarehouse { get; set; }
+ public Measure ProductMeasure { get; set; }
+ public double? DeliveryMakeUp { get; set; }
+
+ [Column(TypeName = "decimal(8,2)")]
+ public decimal Price { get; set; }
+
+ public void Dispose()
+ {
+ Debug.WriteLine($"{this} disposed");
+ }
+
+ public override string ToString()
+ {
+ return $"({ProductSku}) {Name}";
+ }
+ }
+} \ No newline at end of file
diff --git a/RhSolutions.Api/Models/RhsolutionsContext.cs b/RhSolutions.Api/Models/RhsolutionsContext.cs
new file mode 100644
index 0000000..2da0783
--- /dev/null
+++ b/RhSolutions.Api/Models/RhsolutionsContext.cs
@@ -0,0 +1,11 @@
+using Microsoft.EntityFrameworkCore;
+
+namespace RhSolutions.Api.Models;
+
+public class RhSolutionsContext : DbContext
+{
+ public RhSolutionsContext(DbContextOptions<RhSolutionsContext> options)
+ : base(options) { }
+
+ public DbSet<Product> Products => Set<Product>();
+}
diff --git a/RhSolutions.Api/Models/Sku.cs b/RhSolutions.Api/Models/Sku.cs
new file mode 100644
index 0000000..c1a4349
--- /dev/null
+++ b/RhSolutions.Api/Models/Sku.cs
@@ -0,0 +1,127 @@
+using System.ComponentModel.DataAnnotations;
+using System.Text.RegularExpressions;
+
+namespace RhSolutions.Api.Models
+{
+ public class Sku
+ {
+ private const string matchPattern = @"([1\D]|\b)(?<Article>\d{6})([1\s-]|)(?<Variant>\d{3})\b";
+ private string? _article;
+ private string? _variant;
+
+ public Sku(string article, string variant)
+ {
+ Article = article;
+ Variant = variant;
+ }
+
+ [Key]
+ public string Id
+ {
+ get
+ {
+ return $"1{Article}1{Variant}";
+ }
+ set
+ {
+ if (TryParse(value, out IEnumerable<Sku> skus))
+ {
+ if (skus.Count() > 1)
+ {
+ throw new ArgumentException($"More than one valid sku detected: {value}");
+ }
+ else
+ {
+ this.Article = skus.First().Article;
+ this.Variant = skus.First().Variant;
+ }
+ }
+ else
+ {
+ throw new ArgumentException($"Invalid sku input: {value}");
+ }
+ }
+ }
+ public string? Article
+ {
+ get
+ {
+ return _article;
+ }
+ set
+ {
+ if (value == null || value.Length != 6 || value.Where(c => char.IsDigit(c)).Count() != 6)
+ {
+ throw new ArgumentException($"Wrong Article: {Article}");
+ }
+ else
+ {
+ _article = value;
+ }
+ }
+ }
+ public string? Variant
+ {
+ get
+ {
+ return _variant;
+ }
+ set
+ {
+ if (value == null || value.Length != 3 || value.Where(c => char.IsDigit(c)).Count() != 3)
+ {
+ throw new ArgumentException($"Wrong Variant: {Variant}");
+ }
+ else _variant = value;
+ }
+ }
+ public static IEnumerable<Sku> GetValidSkus(string line)
+ {
+ MatchCollection matches = Regex.Matches(line, matchPattern);
+ if (matches.Count == 0)
+ {
+ yield break;
+ }
+ else
+ {
+ foreach (Match m in matches)
+ {
+ yield return new Sku(m.Groups["Article"].Value, m.Groups["Variant"].Value);
+ }
+ }
+ }
+
+ public static bool TryParse(string line, out IEnumerable<Sku> skus)
+ {
+ MatchCollection matches = Regex.Matches(line, matchPattern);
+ if (matches.Count == 0)
+ {
+ skus = Enumerable.Empty<Sku>();
+ return false;
+ }
+
+ else
+ {
+ skus = GetValidSkus(line);
+ return true;
+ }
+ }
+
+ public override bool Equals(object? obj)
+ {
+ return obj is Sku sku &&
+ Article!.Equals(sku.Article) &&
+ Variant!.Equals(sku.Variant);
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(Article, Variant);
+ }
+
+ public override string ToString()
+ {
+ return $"1{Article}1{Variant}";
+ }
+ }
+} \ No newline at end of file
diff --git a/RhSolutions.Api/Program.cs b/RhSolutions.Api/Program.cs
new file mode 100644
index 0000000..1defdda
--- /dev/null
+++ b/RhSolutions.Api/Program.cs
@@ -0,0 +1,34 @@
+using Microsoft.EntityFrameworkCore;
+using RhSolutions.Api.Models;
+using RhSolutions.Api.Services;
+
+var builder = WebApplication.CreateBuilder(args);
+
+string dbHost = builder.Configuration["DB_HOST"],
+ dbPort = builder.Configuration["DB_PORT"],
+ dbName = builder.Configuration["DB_DATABASE"],
+ dbUser = builder.Configuration["DB_USER"],
+ dbPassword = builder.Configuration["DB_PASSWORD"];
+
+string connectionString = builder.Configuration["ConnectionsStrings:RhSolutionsLocal"]
+ ?? $"Host={dbHost};Port={dbPort};Database={dbName};Username={dbUser};Password={dbPassword}";
+
+builder.Services.AddDbContext<RhSolutionsContext>(opts =>
+{
+ opts.UseNpgsql(connectionString);
+ if (builder.Environment.IsDevelopment())
+ {
+ opts.EnableSensitiveDataLogging(true);
+ }
+});
+builder.Services.AddScoped<IPricelistParser, ClosedXMLParser>();
+builder.Services.AddControllers();
+
+var app = builder.Build();
+
+app.MapControllers();
+
+var context = app.Services.CreateScope().ServiceProvider
+ .GetRequiredService<RhSolutionsContext>();
+
+app.Run();
diff --git a/RhSolutions.Api/Properties/launchSettings.json b/RhSolutions.Api/Properties/launchSettings.json
new file mode 100644
index 0000000..9c800c5
--- /dev/null
+++ b/RhSolutions.Api/Properties/launchSettings.json
@@ -0,0 +1,29 @@
+{
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "launchBrowser": false,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:5000",
+ "sslPort": 0
+ }
+ },
+ "profiles": {
+ "RhSolutions": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": false,
+ "applicationUrl": "http://localhost:5000",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": false,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/RhSolutions.Api/RhSolutions.Api.csproj b/RhSolutions.Api/RhSolutions.Api.csproj
new file mode 100644
index 0000000..1e6dd7f
--- /dev/null
+++ b/RhSolutions.Api/RhSolutions.Api.csproj
@@ -0,0 +1,20 @@
+<Project Sdk="Microsoft.NET.Sdk.Web">
+
+ <PropertyGroup>
+ <TargetFramework>net6.0</TargetFramework>
+ <Nullable>enable</Nullable>
+ <ImplicitUsings>enable</ImplicitUsings>
+ <UserSecretsId>1c307973-55cf-4d5c-a4f8-1def6b58ee3c</UserSecretsId>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="ClosedXML" Version="0.97.0" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.0">
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ <PrivateAssets>all</PrivateAssets>
+ </PackageReference>
+ <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="7.0.0" />
+ <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.Design" Version="1.1.0" />
+ </ItemGroup>
+
+</Project>
diff --git a/RhSolutions.Api/Services/ClosedXMLParser.cs b/RhSolutions.Api/Services/ClosedXMLParser.cs
new file mode 100644
index 0000000..1a74528
--- /dev/null
+++ b/RhSolutions.Api/Services/ClosedXMLParser.cs
@@ -0,0 +1,159 @@
+using ClosedXML.Excel;
+using RhSolutions.Api.Models;
+
+namespace RhSolutions.Api.Services
+{
+ public class ClosedXMLParser : IPricelistParser
+ {
+ // HttpContext? context;
+ // XLWorkbook? wb;
+
+ // public ClosedXMLParser(IHttpContextAccessor accessor)
+ // {
+ // this.context = accessor.HttpContext;
+ // }
+
+ // public void Dispose()
+ // {
+ // wb?.Dispose();
+ // }
+
+ public async IAsyncEnumerable<Product> GetProducts(HttpContext context)
+ {
+ using (var memoryStream = new MemoryStream())
+ {
+ if (context == null)
+ {
+ yield break;
+ }
+
+ await context.Request.Body.CopyToAsync(memoryStream);
+ using (var wb = new XLWorkbook(memoryStream))
+ {
+ var table = GetTable(wb);
+ var rows = table.DataRange.Rows();
+ var enumerator = rows.GetEnumerator();
+ while (enumerator.MoveNext())
+ {
+ if (Sku.TryParse(enumerator.Current.Field("Актуальный материал")
+ .GetString(), out IEnumerable<Sku> skus))
+ {
+ yield return ParseRow(enumerator.Current);
+ }
+ }
+ yield break;
+ }
+ }
+ }
+
+ // private async Task<XLWorkbook> GetWorkbook(HttpContext context)
+ // {
+ // using var memoryStream = new MemoryStream();
+ // return ;
+ // }
+
+ private IXLTable GetTable(XLWorkbook wb)
+ {
+ var ws = wb.Worksheets.First();
+ ws.AutoFilter.IsEnabled = false;
+ try
+ {
+ var firstCellAddress = ws.Search("Программа", System.Globalization.CompareOptions.IgnoreCase)
+ .First()
+ .Address;
+
+ var lastCellAddress = ws.LastCellUsed().Address;
+
+ return ws.Range(firstCellAddress, lastCellAddress).AsTable();
+ }
+ catch (Exception ex)
+ {
+ throw new Exception($"Exception on parsing {ws.Name}:\n{ex.Message}");
+ }
+ }
+
+ private Product ParseRow(IXLTableRow row)
+ {
+ string productLine = row.Field("Программа")
+ .GetString();
+ string productName = row.Field("Наименование")
+ .GetString()
+ .Split('\n')
+ .First();
+ Sku.TryParse(row.Field("Актуальный материал")
+ .GetString(), out IEnumerable<Sku> productSkus);
+ Sku.TryParse(row.Field("Прежний материал")
+ .GetString(), out IEnumerable<Sku> deprecatedSkus);
+
+ string measureField = new string(row.Field("Ед. изм.")
+ .GetString()
+ .ToLower()
+ .Where(c => char.IsLetterOrDigit(c))
+ .ToArray());
+ Measure productMeasure;
+
+ switch (measureField)
+ {
+ case "кг":
+ productMeasure = Measure.Kg;
+ break;
+ case "м":
+ productMeasure = Measure.M;
+ break;
+ case "м2":
+ productMeasure = Measure.M2;
+ break;
+ case "шт":
+ productMeasure = Measure.P;
+ break;
+ default:
+ throw new ArgumentException();
+ }
+
+ string shippingSizeField = row.Field("Единица поставки")
+ .GetString();
+
+ if (!double.TryParse(shippingSizeField, out double productWarehouseCount))
+ {
+ productWarehouseCount = 0.0;
+ }
+
+ string onWarehouseField = row.Field("Складская программа")
+ .GetString();
+ bool? IsOnWarehouse;
+
+ switch (onWarehouseField)
+ {
+ case "x":
+ IsOnWarehouse = true;
+ break;
+ case "под заказ":
+ IsOnWarehouse = false;
+ break;
+ default:
+ IsOnWarehouse = null;
+ break;
+ }
+
+ string priceField = row.Field("Цена брутто \nЕВРО\nбез НДС")
+ .GetString();
+
+ if (!decimal.TryParse(priceField, out decimal price))
+ {
+ price = 0.0M;
+ }
+
+ return new Product
+ {
+ ProductLine = productLine,
+ Name = productName,
+ ProductSku = productSkus.First().Id,
+ DeprecatedSkus = deprecatedSkus.Select(s => s.Id).ToList(),
+ ProductMeasure = productMeasure,
+ DeliveryMakeUp = productWarehouseCount,
+ IsOnWarehouse = IsOnWarehouse,
+ Price = price
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/RhSolutions.Api/Services/IPricelistParser.cs b/RhSolutions.Api/Services/IPricelistParser.cs
new file mode 100644
index 0000000..eed1dee
--- /dev/null
+++ b/RhSolutions.Api/Services/IPricelistParser.cs
@@ -0,0 +1,9 @@
+using RhSolutions.Api.Models;
+
+namespace RhSolutions.Api.Services
+{
+ public interface IPricelistParser
+ {
+ public IAsyncEnumerable<Product> GetProducts(HttpContext context);
+ }
+} \ No newline at end of file
diff --git a/RhSolutions.Api/appsettings.Development.json b/RhSolutions.Api/appsettings.Development.json
new file mode 100644
index 0000000..e203e94
--- /dev/null
+++ b/RhSolutions.Api/appsettings.Development.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Debug",
+ "System": "Information",
+ "Microsoft": "Information"
+ }
+ }
+}
diff --git a/RhSolutions.Api/appsettings.json b/RhSolutions.Api/appsettings.json
new file mode 100644
index 0000000..277e051
--- /dev/null
+++ b/RhSolutions.Api/appsettings.json
@@ -0,0 +1,10 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning",
+ "Microsoft.EntityFrameworkCore": "Information"
+ }
+ },
+ "AllowedHosts": "*"
+ }
diff --git a/RhSolutions.Api/global.json b/RhSolutions.Api/global.json
new file mode 100644
index 0000000..2948868
--- /dev/null
+++ b/RhSolutions.Api/global.json
@@ -0,0 +1,5 @@
+{
+ "sdk": {
+ "version": "6.0.402"
+ }
+} \ No newline at end of file
diff --git a/RhSolutions.Deploy/database/Dockerfile b/RhSolutions.Deploy/database/Dockerfile
new file mode 100644
index 0000000..5d76b63
--- /dev/null
+++ b/RhSolutions.Deploy/database/Dockerfile
@@ -0,0 +1,6 @@
+FROM postgres:latest AS build
+ADD ./init-database.sql /docker-entrypoint-initdb.d
+RUN chmod 644 /docker-entrypoint-initdb.d/init-database.sql
+EXPOSE 5432
+ENTRYPOINT [ "docker-entrypoint.sh" ]
+CMD [ "postgres" ] \ No newline at end of file
diff --git a/RhSolutions.Deploy/database/init-database.sql b/RhSolutions.Deploy/database/init-database.sql
new file mode 100644
index 0000000..ec6ac7d
--- /dev/null
+++ b/RhSolutions.Deploy/database/init-database.sql
@@ -0,0 +1,25 @@
+CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" (
+ "MigrationId" character varying(150) NOT NULL,
+ "ProductVersion" character varying(32) NOT NULL,
+ CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY ("MigrationId")
+);
+
+START TRANSACTION;
+
+CREATE TABLE "Products" (
+ "Id" integer GENERATED BY DEFAULT AS IDENTITY,
+ "ProductSku" text NULL,
+ "DeprecatedSkus" text[] NOT NULL,
+ "Name" text NOT NULL,
+ "ProductLine" text NULL,
+ "IsOnWarehouse" boolean NULL,
+ "ProductMeasure" integer NOT NULL,
+ "DeliveryMakeUp" double precision NULL,
+ "Price" numeric(8,2) NOT NULL,
+ CONSTRAINT "PK_Products" PRIMARY KEY ("Id")
+);
+
+INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
+VALUES ('20221201071323_Init', '7.0.0');
+
+COMMIT; \ No newline at end of file
diff --git a/RhSolutions.Deploy/docker-compose.yml b/RhSolutions.Deploy/docker-compose.yml
new file mode 100644
index 0000000..b5f4ebc
--- /dev/null
+++ b/RhSolutions.Deploy/docker-compose.yml
@@ -0,0 +1,31 @@
+version: '3'
+
+services:
+
+ rhsolutions-api:
+ build: ../RhSolutions.Api
+ container_name: rhsolutions-api
+ ports:
+ - 5000:5000
+ environment:
+ - DB_HOST=rhsolutions-db
+ - DB_PORT=5432
+ - DB_DATABASE=rhsolutions
+ - DB_USER=chebser
+ - DB_PASSWORD=Rehau-987
+ depends_on:
+ - rhsolutions-db
+ restart: unless-stopped
+
+ rhsolutions-db:
+ container_name: rhsolutions-db
+ build: ./database
+ environment:
+ - POSTGRES_USER=chebser
+ - POSTGRES_PASSWORD=Rehau-987
+ - POSTGRES_DB=rhsolutions
+ restart: unless-stopped
+
+networks:
+ default:
+ name: rhsolutions \ No newline at end of file
diff --git a/RhSolutions.sln b/RhSolutions.sln
new file mode 100644
index 0000000..7a75ba9
--- /dev/null
+++ b/RhSolutions.sln
@@ -0,0 +1,28 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RhSolutions.Api", "RhSolutions.Api\RhSolutions.Api.csproj", "{FD778359-7E92-4B5C-A4F9-7942A28E58F5}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RhSolutions.Api.Tests", "RhSolutions.Api.Tests\RhSolutions.Api.Tests.csproj", "{A71CCD18-1D47-4DF5-B883-AF4D1CFB4F4E}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {FD778359-7E92-4B5C-A4F9-7942A28E58F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FD778359-7E92-4B5C-A4F9-7942A28E58F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FD778359-7E92-4B5C-A4F9-7942A28E58F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FD778359-7E92-4B5C-A4F9-7942A28E58F5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A71CCD18-1D47-4DF5-B883-AF4D1CFB4F4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A71CCD18-1D47-4DF5-B883-AF4D1CFB4F4E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A71CCD18-1D47-4DF5-B883-AF4D1CFB4F4E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A71CCD18-1D47-4DF5-B883-AF4D1CFB4F4E}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal