diff --git a/Dockerfile b/Dockerfile index 93139c8..64ce3ad 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,8 @@ RUN dotnet publish -c Release -o out FROM mcr.microsoft.com/dotnet/aspnet:6.0 EXPOSE 5000 +EXPOSE 43000 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 +ENTRYPOINT [ "dotnet", "RhSolutions.Api.dll" ] \ No newline at end of file diff --git a/RhSolutions.Api/Controllers/ProductsController.cs b/RhSolutions.Api/Controllers/ProductsController.cs index 550ea9d..9dfe17a 100644 --- a/RhSolutions.Api/Controllers/ProductsController.cs +++ b/RhSolutions.Api/Controllers/ProductsController.cs @@ -5,78 +5,78 @@ using System.Linq; namespace RhSolutions.Api.Controllers { - [Route("api/[controller]")] - public class ProductsController : ControllerBase - { - private RhSolutionsContext dbContext; - private IPricelistParser parser; + [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; - } + public ProductsController(RhSolutionsContext dbContext, IPricelistParser parser) + { + this.dbContext = dbContext; + this.parser = parser; + } - [HttpGet] - public IAsyncEnumerable GetProducts() - { - return dbContext.Products - .AsAsyncEnumerable(); - } + [HttpGet] + public IAsyncEnumerable GetProducts() + { + return dbContext.Products + .AsAsyncEnumerable(); + } - [HttpGet("{id}")] - public IEnumerable GetProduct(string id) - { - return dbContext.Products - .Where(p => p.Id.Equals(id)); - } + [HttpGet("{id}")] + public IEnumerable GetProduct(string id) + { + return dbContext.Products + .Where(p => p.Id.Equals(id)); + } - [HttpPost] - public IActionResult PostProductsFromXls() - { - try - { - var products = parser.GetProducts(HttpContext).GroupBy(p => p.ProductSku) - .Select(g => new Product(g.Key) - { - Name = g.First().Name, - DeprecatedSkus = g.SelectMany(p => p.DeprecatedSkus).Distinct().ToList(), - ProductLines = g.SelectMany(p => p.ProductLines).Distinct().ToList(), - IsOnWarehouse = g.Any(p => p.IsOnWarehouse == true), - ProductMeasure = g.First().ProductMeasure, - DeliveryMakeUp = g.First().DeliveryMakeUp, - Price = g.First().Price - }); + [HttpPost] + public IActionResult PostProductsFromXls() + { + try + { + var products = parser.GetProducts(HttpContext).GroupBy(p => p.ProductSku) + .Select(g => new Product(g.Key) + { + Name = g.First().Name, + DeprecatedSkus = g.SelectMany(p => p.DeprecatedSkus).Distinct().ToList(), + ProductLines = g.SelectMany(p => p.ProductLines).Distinct().ToList(), + IsOnWarehouse = g.Any(p => p.IsOnWarehouse == true), + ProductMeasure = g.First().ProductMeasure, + DeliveryMakeUp = g.First().DeliveryMakeUp, + Price = g.First().Price + }); - foreach (var p in products) - { - dbContext.Add(p); - } + foreach (var p in products) + { + dbContext.Add(p); + } - dbContext.SaveChanges(); - return Ok(); - } - catch (Exception ex) - { - return BadRequest(ex.Message); - } - } + dbContext.SaveChanges(); + return Ok(); + } + catch (Exception ex) + { + return BadRequest(ex.Message); + } + } - [HttpDelete] - public IActionResult DeleteAllProducts() - { - List 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"); - } - } + [HttpDelete] + public IActionResult DeleteAllProducts() + { + List 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/Program.cs b/RhSolutions.Api/Program.cs index 006e312..4756ec8 100644 --- a/RhSolutions.Api/Program.cs +++ b/RhSolutions.Api/Program.cs @@ -4,7 +4,7 @@ using RhSolutions.Api.Services; using RhSolutions.Api.Middleware; using RhSolutions.QueryModifiers; -var builder = WebApplication.CreateBuilder(args); +var builder = WebApplication.CreateBuilder(); string dbHost = builder.Configuration["DB_HOST"], dbPort = builder.Configuration["DB_PORT"], @@ -23,17 +23,18 @@ builder.Services.AddDbContext(opts => opts.EnableSensitiveDataLogging(true); } }); + builder.Services.AddScoped() .AddScoped() - .AddSingleton(); + .AddSingleton() + .AddGrpc(); + builder.Services.AddControllers(); var app = builder.Build(); app.MapControllers(); +app.MapGrpcService(); app.UseMiddleware(); -var context = app.Services.CreateScope().ServiceProvider - .GetRequiredService(); - app.Run(); diff --git a/RhSolutions.Api/Properties/launchSettings.json b/RhSolutions.Api/Properties/launchSettings.json index 9c800c5..24e487d 100644 --- a/RhSolutions.Api/Properties/launchSettings.json +++ b/RhSolutions.Api/Properties/launchSettings.json @@ -4,7 +4,7 @@ "anonymousAuthentication": true, "launchBrowser": false, "iisExpress": { - "applicationUrl": "http://localhost:5000", + "applicationUrl": "http://localhost:5000;http://localhost:43000", "sslPort": 0 } }, @@ -13,7 +13,7 @@ "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": false, - "applicationUrl": "http://localhost:5000", + "applicationUrl": "http://localhost:5000;http://localhost:43000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/RhSolutions.Api/Protos/product.proto b/RhSolutions.Api/Protos/product.proto new file mode 100644 index 0000000..f972020 --- /dev/null +++ b/RhSolutions.Api/Protos/product.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +service ProductSearch { + rpc GetProduct (ProductRequest) returns (ProductReply); +} + +message ProductRequest { + string query = 1; +} + +message ProductReply { + string id = 1; + string name = 2; + double price = 3; +} \ No newline at end of file diff --git a/RhSolutions.Api/RhSolutions.Api.csproj b/RhSolutions.Api/RhSolutions.Api.csproj index 507302e..df7f167 100644 --- a/RhSolutions.Api/RhSolutions.Api.csproj +++ b/RhSolutions.Api/RhSolutions.Api.csproj @@ -9,6 +9,11 @@ + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -29,4 +34,9 @@ + + + + + diff --git a/RhSolutions.Api/Services/ProductTypePredicter.cs b/RhSolutions.Api/Services/ProductTypePredicter.cs index 2c2c226..77592dd 100644 --- a/RhSolutions.Api/Services/ProductTypePredicter.cs +++ b/RhSolutions.Api/Services/ProductTypePredicter.cs @@ -5,40 +5,40 @@ namespace RhSolutions.Api.Services; public class ProductTypePredicter : IProductTypePredicter { - private readonly string _modelPath = @"./MLModels/model.zip"; - private MLContext _mlContext; - private ITransformer _loadedModel; - private PredictionEngine _predEngine; + private readonly string _modelPath = @"./MLModels/model.zip"; + private MLContext _mlContext; + private ITransformer _loadedModel; + private PredictionEngine _predEngine; - public ProductTypePredicter() - { - _mlContext = new MLContext(seed: 0); - _loadedModel = _mlContext.Model.Load(_modelPath, out var _); - _predEngine = _mlContext.Model.CreatePredictionEngine(_loadedModel); - } + public ProductTypePredicter() + { + _mlContext = new MLContext(seed: 0); + _loadedModel = _mlContext.Model.Load(_modelPath, out var _); + _predEngine = _mlContext.Model.CreatePredictionEngine(_loadedModel); + } - public string? GetPredictedProductType(string productName) - { - Product p = new() - { - Name = productName - }; - var prediction = _predEngine.Predict(p); - return prediction.Type; - } + public string? GetPredictedProductType(string productName) + { + Product p = new() + { + Name = productName + }; + var prediction = _predEngine.Predict(p); + return prediction.Type; + } - public class Product - { - [LoadColumn(0)] - public string? Name { get; set; } - [LoadColumn(1)] - public string? Type { get; set; } - } + public class Product + { + [LoadColumn(0)] + public string? Name { get; set; } + [LoadColumn(1)] + public string? Type { get; set; } + } - public class TypePrediction - { - [ColumnName("PredictedLabel")] - public string? Type { get; set; } - } + public class TypePrediction + { + [ColumnName("PredictedLabel")] + public string? Type { get; set; } + } } diff --git a/RhSolutions.Api/Services/SearchService.cs b/RhSolutions.Api/Services/SearchService.cs new file mode 100644 index 0000000..95e4b05 --- /dev/null +++ b/RhSolutions.Api/Services/SearchService.cs @@ -0,0 +1,48 @@ +using Grpc.Core; +using RhSolutions.Models; +using Microsoft.EntityFrameworkCore; +using RhSolutions.QueryModifiers; + +namespace RhSolutions.Api.Services; + +public class SearchService : ProductSearch.ProductSearchBase +{ + private RhSolutionsContext _dbContext; + private IProductTypePredicter _typePredicter; + private ProductQueryModifierFactory _productQueryModifierFactory; + + public SearchService(RhSolutionsContext dbContext, IProductTypePredicter typePredicter, ProductQueryModifierFactory productQueryModifierFactory) + { + _dbContext = dbContext; + _typePredicter = typePredicter; + _productQueryModifierFactory = productQueryModifierFactory; + } + public override async Task GetProduct(ProductRequest request, ServerCallContext context) + { + var productType = _typePredicter.GetPredictedProductType(request.Query); + var modifier = _productQueryModifierFactory.GetModifier(productType!); + string query = request.Query; + if (modifier.TryQueryModify(query, out var modified)) + { + query = modified; + } + var product = await _dbContext.Products + .Where(p => EF.Functions.ToTsVector( + "russian", string.Join(' ', new[] { p.Name, string.Join(' ', p.ProductLines) })) + .Matches(EF.Functions.WebSearchToTsQuery("russian", query))) + .OrderByDescending(p => p.IsOnWarehouse) + .FirstOrDefaultAsync(); + + if (product != null) + { + return new ProductReply() + { + Id = product.Id, + Name = product.Name, + Price = (double)product.Price + }; + } + return null; + } +} + diff --git a/RhSolutions.Api/appsettings.json b/RhSolutions.Api/appsettings.json index 277e051..a5bb9b5 100644 --- a/RhSolutions.Api/appsettings.json +++ b/RhSolutions.Api/appsettings.json @@ -6,5 +6,17 @@ "Microsoft.EntityFrameworkCore": "Information" } }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "Kestrel": { + "Endpoints": { + "Http": { + "Url": "http://0.0.0.0:5000", + "Protocols": "Http1AndHttp2" + }, + "gRPC": { + "Url": "http://0.0.0.0:43000", + "Protocols": "Http2" + } + } } +} \ No newline at end of file