diff --git a/samples/MultiTenancySample/MultiTenancySample.DatabaseIsolationService/Controllers/ProductsController.cs b/samples/MultiTenancySample/MultiTenancySample.DatabaseIsolationService/Controllers/ProductsController.cs index eb57fe1..cfbf2d9 100644 --- a/samples/MultiTenancySample/MultiTenancySample.DatabaseIsolationService/Controllers/ProductsController.cs +++ b/samples/MultiTenancySample/MultiTenancySample.DatabaseIsolationService/Controllers/ProductsController.cs @@ -1,4 +1,7 @@ -using Microsoft.AspNetCore.Mvc; +// Copyright (c) HelloShop Corporation. All rights reserved. +// See the license file in the project root for more information. + +using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using MultiTenancySample.DatabaseIsolationService.Entities; using MultiTenancySample.DatabaseIsolationService.EntityFrameworks; @@ -23,5 +26,14 @@ namespace MultiTenancySample.DatabaseIsolationService.Controllers return Ok(products); } + + [HttpPost] + public async Task CreateProduct(Product product) + { + await _dbContext.AddAsync(product); + await _dbContext.SaveChangesAsync(); + + return Ok(product); + } } } diff --git a/samples/MultiTenancySample/MultiTenancySample.DatabaseIsolationService/Entities/Product.cs b/samples/MultiTenancySample/MultiTenancySample.DatabaseIsolationService/Entities/Product.cs index e21a15b..4cca264 100644 --- a/samples/MultiTenancySample/MultiTenancySample.DatabaseIsolationService/Entities/Product.cs +++ b/samples/MultiTenancySample/MultiTenancySample.DatabaseIsolationService/Entities/Product.cs @@ -1,4 +1,7 @@ -namespace MultiTenancySample.DatabaseIsolationService.Entities +// Copyright (c) HelloShop Corporation. All rights reserved. +// See the license file in the project root for more information. + +namespace MultiTenancySample.DatabaseIsolationService.Entities { public class Product { diff --git a/samples/MultiTenancySample/MultiTenancySample.DatabaseIsolationService/EntityFrameworks/DataSeeding.cs b/samples/MultiTenancySample/MultiTenancySample.DatabaseIsolationService/EntityFrameworks/DataSeeding.cs index d4309f8..973b125 100644 --- a/samples/MultiTenancySample/MultiTenancySample.DatabaseIsolationService/EntityFrameworks/DataSeeding.cs +++ b/samples/MultiTenancySample/MultiTenancySample.DatabaseIsolationService/EntityFrameworks/DataSeeding.cs @@ -1,4 +1,7 @@ -using Microsoft.EntityFrameworkCore; +// Copyright (c) HelloShop Corporation. All rights reserved. +// See the license file in the project root for more information. + +using Microsoft.EntityFrameworkCore; using MultiTenancySample.DatabaseIsolationService.Entities; using MultiTenancySample.ServiceDefaults; diff --git a/samples/MultiTenancySample/MultiTenancySample.DatabaseIsolationService/EntityFrameworks/DatabaseIsolationServiceDbContext.cs b/samples/MultiTenancySample/MultiTenancySample.DatabaseIsolationService/EntityFrameworks/DatabaseIsolationServiceDbContext.cs index cf32e91..364ddb5 100644 --- a/samples/MultiTenancySample/MultiTenancySample.DatabaseIsolationService/EntityFrameworks/DatabaseIsolationServiceDbContext.cs +++ b/samples/MultiTenancySample/MultiTenancySample.DatabaseIsolationService/EntityFrameworks/DatabaseIsolationServiceDbContext.cs @@ -1,4 +1,7 @@ -using Microsoft.EntityFrameworkCore; +// Copyright (c) HelloShop Corporation. All rights reserved. +// See the license file in the project root for more information. + +using Microsoft.EntityFrameworkCore; using MultiTenancySample.DatabaseIsolationService.Entities; using MultiTenancySample.ServiceDefaults; diff --git a/samples/MultiTenancySample/MultiTenancySample.DatabaseIsolationService/Program.cs b/samples/MultiTenancySample/MultiTenancySample.DatabaseIsolationService/Program.cs index 43c67a6..4d5e8cf 100644 --- a/samples/MultiTenancySample/MultiTenancySample.DatabaseIsolationService/Program.cs +++ b/samples/MultiTenancySample/MultiTenancySample.DatabaseIsolationService/Program.cs @@ -1,3 +1,6 @@ +// Copyright (c) HelloShop Corporation. All rights reserved. +// See the license file in the project root for more information. + using MultiTenancySample.DatabaseIsolationService.EntityFrameworks; using MultiTenancySample.ServiceDefaults; diff --git a/samples/MultiTenancySample/MultiTenancySample.FieldIsolationService/Controllers/ProductsController.cs b/samples/MultiTenancySample/MultiTenancySample.FieldIsolationService/Controllers/ProductsController.cs index 44a9d8a..d90318e 100644 --- a/samples/MultiTenancySample/MultiTenancySample.FieldIsolationService/Controllers/ProductsController.cs +++ b/samples/MultiTenancySample/MultiTenancySample.FieldIsolationService/Controllers/ProductsController.cs @@ -1,4 +1,7 @@ -using Microsoft.AspNetCore.Mvc; +// Copyright (c) HelloShop Corporation. All rights reserved. +// See the license file in the project root for more information. + +using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using MultiTenancySample.FieldIsolationService.Entities; using MultiTenancySample.FieldIsolationService.EntityFrameworks; @@ -23,5 +26,15 @@ namespace MultiTenancySample.FieldIsolationService.Controllers return Ok(products); } + + [HttpPost] + public async Task CreateProduct(Product product) + { + await _dbContext.AddAsync(product); + + await _dbContext.SaveChangesAsync(); + + return Ok(product); + } } } diff --git a/samples/MultiTenancySample/MultiTenancySample.FieldIsolationService/Entities/IMultiTenant.cs b/samples/MultiTenancySample/MultiTenancySample.FieldIsolationService/Entities/IMultiTenant.cs index d08b099..d32de6d 100644 --- a/samples/MultiTenancySample/MultiTenancySample.FieldIsolationService/Entities/IMultiTenant.cs +++ b/samples/MultiTenancySample/MultiTenancySample.FieldIsolationService/Entities/IMultiTenant.cs @@ -1,4 +1,7 @@ -namespace MultiTenancySample.FieldIsolationService.Entities +// Copyright (c) HelloShop Corporation. All rights reserved. +// See the license file in the project root for more information. + +namespace MultiTenancySample.FieldIsolationService.Entities { public interface IMultiTenant { diff --git a/samples/MultiTenancySample/MultiTenancySample.FieldIsolationService/Entities/Product.cs b/samples/MultiTenancySample/MultiTenancySample.FieldIsolationService/Entities/Product.cs index 9ce4077..fe92bbf 100644 --- a/samples/MultiTenancySample/MultiTenancySample.FieldIsolationService/Entities/Product.cs +++ b/samples/MultiTenancySample/MultiTenancySample.FieldIsolationService/Entities/Product.cs @@ -1,4 +1,7 @@ -using System.Text.Json.Serialization; +// Copyright (c) HelloShop Corporation. All rights reserved. +// See the license file in the project root for more information. + +using System.Text.Json.Serialization; namespace MultiTenancySample.FieldIsolationService.Entities { diff --git a/samples/MultiTenancySample/MultiTenancySample.FieldIsolationService/EntityFrameworks/DataSeeding.cs b/samples/MultiTenancySample/MultiTenancySample.FieldIsolationService/EntityFrameworks/DataSeeding.cs index 284097d..ee5ec20 100644 --- a/samples/MultiTenancySample/MultiTenancySample.FieldIsolationService/EntityFrameworks/DataSeeding.cs +++ b/samples/MultiTenancySample/MultiTenancySample.FieldIsolationService/EntityFrameworks/DataSeeding.cs @@ -1,16 +1,19 @@ -using Microsoft.EntityFrameworkCore; +// Copyright (c) HelloShop Corporation. All rights reserved. +// See the license file in the project root for more information. + +using Microsoft.EntityFrameworkCore; using MultiTenancySample.FieldIsolationService.Entities; using MultiTenancySample.ServiceDefaults; namespace MultiTenancySample.FieldIsolationService.EntityFrameworks { - public class DataSeeding(IDbContextFactory dbContext, ICurrentTenant currentTenant) + public class DataSeeding(IDbContextFactory dbContextFactory, ICurrentTenant currentTenant) { public async Task SeedDataAsync() { currentTenant.SetTenant("Tenant1"); - FieldIsolationServiceDbContext dbContext1 = await dbContext.CreateDbContextAsync(); + FieldIsolationServiceDbContext dbContext1 = await dbContextFactory.CreateDbContextAsync(); if (await dbContext1.Database.EnsureCreatedAsync()) { @@ -25,7 +28,7 @@ namespace MultiTenancySample.FieldIsolationService.EntityFrameworks currentTenant.SetTenant("Tenant2"); - FieldIsolationServiceDbContext dbContext2 = await dbContext.CreateDbContextAsync(); + FieldIsolationServiceDbContext dbContext2 = await dbContextFactory.CreateDbContextAsync(); if (await dbContext2.Set().IgnoreQueryFilters().CountAsync() < 6) { diff --git a/samples/MultiTenancySample/MultiTenancySample.FieldIsolationService/EntityFrameworks/EntityTypeBuilderQueryFilterExtensions.cs b/samples/MultiTenancySample/MultiTenancySample.FieldIsolationService/EntityFrameworks/EntityTypeBuilderQueryFilterExtensions.cs index c96dbf0..7d431b1 100644 --- a/samples/MultiTenancySample/MultiTenancySample.FieldIsolationService/EntityFrameworks/EntityTypeBuilderQueryFilterExtensions.cs +++ b/samples/MultiTenancySample/MultiTenancySample.FieldIsolationService/EntityFrameworks/EntityTypeBuilderQueryFilterExtensions.cs @@ -1,4 +1,7 @@ -using Microsoft.EntityFrameworkCore.Metadata.Builders; +// Copyright (c) HelloShop Corporation. All rights reserved. +// See the license file in the project root for more information. + +using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Query; using System.Linq.Expressions; diff --git a/samples/MultiTenancySample/MultiTenancySample.FieldIsolationService/EntityFrameworks/FieldIsolationServiceDbContext.cs b/samples/MultiTenancySample/MultiTenancySample.FieldIsolationService/EntityFrameworks/FieldIsolationServiceDbContext.cs index 46b209a..70ee0ff 100644 --- a/samples/MultiTenancySample/MultiTenancySample.FieldIsolationService/EntityFrameworks/FieldIsolationServiceDbContext.cs +++ b/samples/MultiTenancySample/MultiTenancySample.FieldIsolationService/EntityFrameworks/FieldIsolationServiceDbContext.cs @@ -1,4 +1,7 @@ -using Microsoft.EntityFrameworkCore; +// Copyright (c) HelloShop Corporation. All rights reserved. +// See the license file in the project root for more information. + +using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; using MultiTenancySample.FieldIsolationService.Entities; using MultiTenancySample.ServiceDefaults; diff --git a/samples/MultiTenancySample/MultiTenancySample.FieldIsolationService/EntityFrameworks/TenantSaveChangesInterceptor.cs b/samples/MultiTenancySample/MultiTenancySample.FieldIsolationService/EntityFrameworks/TenantSaveChangesInterceptor.cs index 20435fb..495366f 100644 --- a/samples/MultiTenancySample/MultiTenancySample.FieldIsolationService/EntityFrameworks/TenantSaveChangesInterceptor.cs +++ b/samples/MultiTenancySample/MultiTenancySample.FieldIsolationService/EntityFrameworks/TenantSaveChangesInterceptor.cs @@ -1,4 +1,7 @@ -using Microsoft.EntityFrameworkCore; +// Copyright (c) HelloShop Corporation. All rights reserved. +// See the license file in the project root for more information. + +using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Diagnostics; using MultiTenancySample.FieldIsolationService.Entities; diff --git a/samples/MultiTenancySample/MultiTenancySample.FieldIsolationService/Program.cs b/samples/MultiTenancySample/MultiTenancySample.FieldIsolationService/Program.cs index 520be5e..19a01d2 100644 --- a/samples/MultiTenancySample/MultiTenancySample.FieldIsolationService/Program.cs +++ b/samples/MultiTenancySample/MultiTenancySample.FieldIsolationService/Program.cs @@ -1,3 +1,6 @@ +// Copyright (c) HelloShop Corporation. All rights reserved. +// See the license file in the project root for more information. + using Microsoft.EntityFrameworkCore; using MultiTenancySample.FieldIsolationService.EntityFrameworks; using MultiTenancySample.ServiceDefaults; diff --git a/samples/MultiTenancySample/MultiTenancySample.SchemaIsolationService/Controllers/ProductsController.cs b/samples/MultiTenancySample/MultiTenancySample.SchemaIsolationService/Controllers/ProductsController.cs index 2690a1b..c739b1c 100644 --- a/samples/MultiTenancySample/MultiTenancySample.SchemaIsolationService/Controllers/ProductsController.cs +++ b/samples/MultiTenancySample/MultiTenancySample.SchemaIsolationService/Controllers/ProductsController.cs @@ -1,4 +1,7 @@ -using Microsoft.AspNetCore.Mvc; +// Copyright (c) HelloShop Corporation. All rights reserved. +// See the license file in the project root for more information. + +using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using MultiTenancySample.SchemaIsolationService.Entities; using MultiTenancySample.SchemaIsolationService.EntityFrameworks; diff --git a/samples/MultiTenancySample/MultiTenancySample.SchemaIsolationService/Entities/Product.cs b/samples/MultiTenancySample/MultiTenancySample.SchemaIsolationService/Entities/Product.cs index 27c2859..a1ccfd7 100644 --- a/samples/MultiTenancySample/MultiTenancySample.SchemaIsolationService/Entities/Product.cs +++ b/samples/MultiTenancySample/MultiTenancySample.SchemaIsolationService/Entities/Product.cs @@ -1,4 +1,7 @@ -namespace MultiTenancySample.SchemaIsolationService.Entities +// Copyright (c) HelloShop Corporation. All rights reserved. +// See the license file in the project root for more information. + +namespace MultiTenancySample.SchemaIsolationService.Entities { public class Product { diff --git a/samples/MultiTenancySample/MultiTenancySample.SchemaIsolationService/EntityFrameworks/DataSeeding.cs b/samples/MultiTenancySample/MultiTenancySample.SchemaIsolationService/EntityFrameworks/DataSeeding.cs index be8a4ec..9339c5a 100644 --- a/samples/MultiTenancySample/MultiTenancySample.SchemaIsolationService/EntityFrameworks/DataSeeding.cs +++ b/samples/MultiTenancySample/MultiTenancySample.SchemaIsolationService/EntityFrameworks/DataSeeding.cs @@ -1,4 +1,7 @@ -using Microsoft.EntityFrameworkCore; +// Copyright (c) HelloShop Corporation. All rights reserved. +// See the license file in the project root for more information. + +using Microsoft.EntityFrameworkCore; using MultiTenancySample.SchemaIsolationService.Entities; using MultiTenancySample.ServiceDefaults; diff --git a/samples/MultiTenancySample/MultiTenancySample.SchemaIsolationService/EntityFrameworks/SchemaIsolationDbContext.cs b/samples/MultiTenancySample/MultiTenancySample.SchemaIsolationService/EntityFrameworks/SchemaIsolationDbContext.cs index 5a803a0..3d1586a 100644 --- a/samples/MultiTenancySample/MultiTenancySample.SchemaIsolationService/EntityFrameworks/SchemaIsolationDbContext.cs +++ b/samples/MultiTenancySample/MultiTenancySample.SchemaIsolationService/EntityFrameworks/SchemaIsolationDbContext.cs @@ -1,4 +1,7 @@ -using Microsoft.EntityFrameworkCore; +// Copyright (c) HelloShop Corporation. All rights reserved. +// See the license file in the project root for more information. + +using Microsoft.EntityFrameworkCore; using MultiTenancySample.SchemaIsolationService.Entities; using MultiTenancySample.ServiceDefaults; diff --git a/samples/MultiTenancySample/MultiTenancySample.SchemaIsolationService/Program.cs b/samples/MultiTenancySample/MultiTenancySample.SchemaIsolationService/Program.cs index 5b1d09d..8753a7a 100644 --- a/samples/MultiTenancySample/MultiTenancySample.SchemaIsolationService/Program.cs +++ b/samples/MultiTenancySample/MultiTenancySample.SchemaIsolationService/Program.cs @@ -1,3 +1,6 @@ +// Copyright (c) HelloShop Corporation. All rights reserved. +// See the license file in the project root for more information. + using Microsoft.EntityFrameworkCore; using MultiTenancySample.SchemaIsolationService.EntityFrameworks; using MultiTenancySample.ServiceDefaults; diff --git a/samples/MultiTenancySample/MultiTenancySample.ServiceDefaults/CurrentTenant.cs b/samples/MultiTenancySample/MultiTenancySample.ServiceDefaults/CurrentTenant.cs index 108e6db..aed9971 100644 --- a/samples/MultiTenancySample/MultiTenancySample.ServiceDefaults/CurrentTenant.cs +++ b/samples/MultiTenancySample/MultiTenancySample.ServiceDefaults/CurrentTenant.cs @@ -1,4 +1,7 @@ -namespace MultiTenancySample.ServiceDefaults +// Copyright (c) HelloShop Corporation. All rights reserved. +// See the license file in the project root for more information. + +namespace MultiTenancySample.ServiceDefaults { public class CurrentTenant : ICurrentTenant { diff --git a/samples/MultiTenancySample/MultiTenancySample.ServiceDefaults/ICurrentTenant.cs b/samples/MultiTenancySample/MultiTenancySample.ServiceDefaults/ICurrentTenant.cs index 4cda38a..c8cf1d6 100644 --- a/samples/MultiTenancySample/MultiTenancySample.ServiceDefaults/ICurrentTenant.cs +++ b/samples/MultiTenancySample/MultiTenancySample.ServiceDefaults/ICurrentTenant.cs @@ -1,4 +1,7 @@ -namespace MultiTenancySample.ServiceDefaults +// Copyright (c) HelloShop Corporation. All rights reserved. +// See the license file in the project root for more information. + +namespace MultiTenancySample.ServiceDefaults { public interface ICurrentTenant { diff --git a/samples/MultiTenancySample/MultiTenancySample.ServiceDefaults/ITenantIdProvider.cs b/samples/MultiTenancySample/MultiTenancySample.ServiceDefaults/ITenantIdProvider.cs index 058b53e..663c2c4 100644 --- a/samples/MultiTenancySample/MultiTenancySample.ServiceDefaults/ITenantIdProvider.cs +++ b/samples/MultiTenancySample/MultiTenancySample.ServiceDefaults/ITenantIdProvider.cs @@ -1,4 +1,7 @@ -namespace MultiTenancySample.ServiceDefaults +// Copyright (c) HelloShop Corporation. All rights reserved. +// See the license file in the project root for more information. + +namespace MultiTenancySample.ServiceDefaults { public interface ITenantIdProvider { diff --git a/samples/MultiTenancySample/MultiTenancySample.ServiceDefaults/MultiTenancyExtensions.cs b/samples/MultiTenancySample/MultiTenancySample.ServiceDefaults/MultiTenancyExtensions.cs index 2a6f0d1..2304010 100644 --- a/samples/MultiTenancySample/MultiTenancySample.ServiceDefaults/MultiTenancyExtensions.cs +++ b/samples/MultiTenancySample/MultiTenancySample.ServiceDefaults/MultiTenancyExtensions.cs @@ -1,4 +1,7 @@ -using Microsoft.AspNetCore.Builder; +// Copyright (c) HelloShop Corporation. All rights reserved. +// See the license file in the project root for more information. + +using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; namespace MultiTenancySample.ServiceDefaults diff --git a/samples/MultiTenancySample/MultiTenancySample.ServiceDefaults/TenantIdProvider.cs b/samples/MultiTenancySample/MultiTenancySample.ServiceDefaults/TenantIdProvider.cs index c4990d1..5e30c73 100644 --- a/samples/MultiTenancySample/MultiTenancySample.ServiceDefaults/TenantIdProvider.cs +++ b/samples/MultiTenancySample/MultiTenancySample.ServiceDefaults/TenantIdProvider.cs @@ -1,4 +1,7 @@ -using Microsoft.AspNetCore.Http; +// Copyright (c) HelloShop Corporation. All rights reserved. +// See the license file in the project root for more information. + +using Microsoft.AspNetCore.Http; namespace MultiTenancySample.ServiceDefaults { @@ -10,6 +13,11 @@ namespace MultiTenancySample.ServiceDefaults const string tenantKey = "tenant"; + if (httpContext.User.FindAll(tenantKey).Any()) + { + return httpContext.User.FindFirst(tenantKey)?.Value; + } + if (httpContext.Request.Headers.TryGetValue(tenantKey, out var headerValues)) { return headerValues.First(); diff --git a/samples/MultiTenancySample/MultiTenancySample.ServiceDefaults/TenantMiddleware.cs b/samples/MultiTenancySample/MultiTenancySample.ServiceDefaults/TenantMiddleware.cs index 1df756a..df864b1 100644 --- a/samples/MultiTenancySample/MultiTenancySample.ServiceDefaults/TenantMiddleware.cs +++ b/samples/MultiTenancySample/MultiTenancySample.ServiceDefaults/TenantMiddleware.cs @@ -1,4 +1,7 @@ -using Microsoft.AspNetCore.Http; +// Copyright (c) HelloShop Corporation. All rights reserved. +// See the license file in the project root for more information. + +using Microsoft.AspNetCore.Http; namespace MultiTenancySample.ServiceDefaults { diff --git a/samples/MultiTenancySample/global.json b/samples/MultiTenancySample/global.json deleted file mode 100644 index 40975d5..0000000 --- a/samples/MultiTenancySample/global.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "sdk": { - "version": "8.0.204", - "rollForward": "latestFeature" - } -} \ No newline at end of file diff --git a/src/HelloShop.ApiService/Extensions/OpenApiConfigureOptions.cs b/src/HelloShop.ApiService/Extensions/OpenApiConfigureOptions.cs index 221a2d3..15cdae8 100644 --- a/src/HelloShop.ApiService/Extensions/OpenApiConfigureOptions.cs +++ b/src/HelloShop.ApiService/Extensions/OpenApiConfigureOptions.cs @@ -21,7 +21,8 @@ public class OpenApiConfigureOptions(IConfiguredServiceEndPointResolver serviceR try { - HttpResponseMessage response = httpClient.GetAsync(uriBuilder.Uri).GetAwaiter().GetResult(); + HttpRequestMessage request = new(HttpMethod.Get, uriBuilder.Uri) { Version = new Version(2, 0) }; + HttpResponseMessage response = httpClient.SendAsync(request).GetAwaiter().GetResult(); if (response.IsSuccessStatusCode) { urlDescriptors.Add(new UrlDescriptor diff --git a/src/HelloShop.ApiService/HelloShop.ApiService.csproj b/src/HelloShop.ApiService/HelloShop.ApiService.csproj index f6b7233..66e34c5 100644 --- a/src/HelloShop.ApiService/HelloShop.ApiService.csproj +++ b/src/HelloShop.ApiService/HelloShop.ApiService.csproj @@ -9,6 +9,6 @@ - + \ No newline at end of file diff --git a/src/HelloShop.AppHost/HelloShop.AppHost.csproj b/src/HelloShop.AppHost/HelloShop.AppHost.csproj index 2a4cfa8..0b4dd96 100644 --- a/src/HelloShop.AppHost/HelloShop.AppHost.csproj +++ b/src/HelloShop.AppHost/HelloShop.AppHost.csproj @@ -15,6 +15,6 @@ - + \ No newline at end of file diff --git a/src/HelloShop.BasketService/AutoMapper/BasketsMapConfiguration.cs b/src/HelloShop.BasketService/AutoMapper/BasketsMapConfiguration.cs new file mode 100644 index 0000000..75d3627 --- /dev/null +++ b/src/HelloShop.BasketService/AutoMapper/BasketsMapConfiguration.cs @@ -0,0 +1,19 @@ +// Copyright (c) HelloShop Corporation. All rights reserved. +// See the license file in the project root for more information. + +using AutoMapper; +using HelloShop.BasketService.Entities; +using HelloShop.BasketService.Protos; + +namespace HelloShop.BasketService.AutoMapper +{ + public class BasketsMapConfiguration : Profile + { + public BasketsMapConfiguration() + { + CreateMap(); + CreateMap().ReverseMap(); + CreateMap(); + } + } +} diff --git a/src/HelloShop.BasketService/Entities/BasketItem.cs b/src/HelloShop.BasketService/Entities/BasketItem.cs new file mode 100644 index 0000000..408bcb5 --- /dev/null +++ b/src/HelloShop.BasketService/Entities/BasketItem.cs @@ -0,0 +1,12 @@ +// Copyright (c) HelloShop Corporation. All rights reserved. +// See the license file in the project root for more information. + +namespace HelloShop.BasketService.Entities +{ + public class BasketItem + { + public int ProductId { get; set; } + + public int Quantity { get; set; } + } +} diff --git a/src/HelloShop.BasketService/Entities/CustomerBasket.cs b/src/HelloShop.BasketService/Entities/CustomerBasket.cs new file mode 100644 index 0000000..dec2512 --- /dev/null +++ b/src/HelloShop.BasketService/Entities/CustomerBasket.cs @@ -0,0 +1,12 @@ +// Copyright (c) HelloShop Corporation. All rights reserved. +// See the license file in the project root for more information. + +namespace HelloShop.BasketService.Entities +{ + public class CustomerBasket + { + public int BuyerId { get; set; } + + public List Items { get; set; } = []; + } +} diff --git a/src/HelloShop.BasketService/HelloShop.BasketService.csproj b/src/HelloShop.BasketService/HelloShop.BasketService.csproj index f7f620c..f555e61 100644 --- a/src/HelloShop.BasketService/HelloShop.BasketService.csproj +++ b/src/HelloShop.BasketService/HelloShop.BasketService.csproj @@ -1,16 +1,24 @@ - - - net8.0 - enable - enable - - - - - - - - - - + + + net8.0 + enable + enable + true + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/HelloShop.BasketService/Program.cs b/src/HelloShop.BasketService/Program.cs index c23d283..e0f29d5 100644 --- a/src/HelloShop.BasketService/Program.cs +++ b/src/HelloShop.BasketService/Program.cs @@ -1,22 +1,61 @@ // Copyright (c) HelloShop Corporation. All rights reserved. // See the license file in the project root for more information. +using Calzolari.Grpc.AspNetCore.Validation; +using HelloShop.BasketService.Repositories; using HelloShop.BasketService.Services; using HelloShop.ServiceDefaults.Extensions; +using Microsoft.IdentityModel.Tokens; +using System.Text; var builder = WebApplication.CreateBuilder(args); builder.AddServiceDefaults(); -// Add services to the container. -builder.Services.AddGrpc(); +// Add extensions services to the container. + +const string issuerSigningKey = HelloShop.ServiceDefaults.Constants.IdentityConstants.IssuerSigningKey; + +builder.Services.AddAuthentication().AddJwtBearer(options => +{ + options.TokenValidationParameters.ValidateIssuer = false; + options.TokenValidationParameters.ValidateAudience = false; + options.TokenValidationParameters.IssuerSigningKey = new SymmetricSecurityKey(Encoding.Default.GetBytes(issuerSigningKey)); +}); + +builder.Services.AddHttpContextAccessor(); +builder.Services.AddDistributedMemoryCache(); +builder.Services.AddSingleton(); + +builder.Services.AddGrpc(options => options.EnableMessageValidation()).AddJsonTranscoding(); +builder.Services.AddGrpcSwagger(); +builder.Services.AddOpenApi(); + +builder.Services.AddDataSeedingProviders(); +builder.Services.AddCustomLocalization(); +builder.Services.AddModelMapper().AddModelValidator(); +builder.Services.AddLocalization().AddPermissionDefinitions(); +builder.Services.AddAuthorization().AddRemotePermissionChecker().AddCustomAuthorization(); +builder.Services.AddGrpcValidation(); +builder.Services.AddCors(); +// End addd extensions services to the container. var app = builder.Build(); +app.UseCors(options => options.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()); + app.MapDefaultEndpoints(); // Configure the HTTP request pipeline. -app.MapGrpcService(); -app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909"); +app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client.").WithTags("Welcome"); +// Configure extensions request pipeline. +app.UseAuthentication().UseAuthorization(); +app.MapGrpcService(); +app.MapGrpcService(); +app.UseDataSeedingProviders(); +app.UseCustomLocalization(); +app.UseOpenApi(); +app.MapGroup("api/Permissions").MapPermissionDefinitions("Permissions"); +// End configure extensions request pipeline. app.Run(); diff --git a/src/HelloShop.BasketService/Properties/launchSettings.json b/src/HelloShop.BasketService/Properties/launchSettings.json index 4938073..eae0e29 100644 --- a/src/HelloShop.BasketService/Properties/launchSettings.json +++ b/src/HelloShop.BasketService/Properties/launchSettings.json @@ -4,7 +4,8 @@ "http": { "commandName": "Project", "dotnetRunMessages": true, - "launchBrowser": false, + "launchBrowser": true, + "launchUrl": "swagger", "applicationUrl": "http://localhost:8004", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" @@ -13,7 +14,8 @@ "https": { "commandName": "Project", "dotnetRunMessages": true, - "launchBrowser": false, + "launchBrowser": true, + "launchUrl": "swagger", "applicationUrl": "https://localhost:8104;http://localhost:8004", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" diff --git a/src/HelloShop.BasketService/Protos/basket.proto b/src/HelloShop.BasketService/Protos/basket.proto new file mode 100644 index 0000000..b09d21e --- /dev/null +++ b/src/HelloShop.BasketService/Protos/basket.proto @@ -0,0 +1,42 @@ +syntax = "proto3"; + +import "google/protobuf/empty.proto"; +import "google/api/annotations.proto"; + +option csharp_namespace = "HelloShop.BasketService.Protos"; + +package basket; + +service Basket { + rpc GetBasket(google.protobuf.Empty) returns (CustomerBasketResponse) { + option (google.api.http) = { + get: "/api/basket" + }; + } + + rpc UpdateBasket(UpdateBasketRequest) returns (CustomerBasketResponse) { + option (google.api.http) = { + put: "/api/basket" + body: "*" + }; + } + + rpc DeleteBasket(google.protobuf.Empty) returns (google.protobuf.Empty) { + option (google.api.http) = { + delete: "/api/basket" + }; + } +} + +message CustomerBasketResponse { + repeated BasketListItem items = 1; +} + +message BasketListItem { + int32 product_id = 1; + int32 quantity = 2; +} + +message UpdateBasketRequest { + repeated BasketListItem items = 1; +} \ No newline at end of file diff --git a/src/HelloShop.BasketService/Protos/greet.proto b/src/HelloShop.BasketService/Protos/greet.proto index b8334fb..72eed6f 100644 --- a/src/HelloShop.BasketService/Protos/greet.proto +++ b/src/HelloShop.BasketService/Protos/greet.proto @@ -1,13 +1,20 @@ syntax = "proto3"; -option csharp_namespace = "HelloShop.BasketService"; +option csharp_namespace = "HelloShop.BasketService.Protos"; + +import "google/api/annotations.proto"; package greet; // The greeting service definition. service Greeter { // Sends a greeting - rpc SayHello (HelloRequest) returns (HelloReply); + rpc SayHello (HelloRequest) returns (HelloReply){ + option (google.api.http) = { + post: "/api/greet" + body: "*" + }; + } } // The request message containing the user's name. diff --git a/src/HelloShop.BasketService/Repositories/DistributedCacheBasketRepository.cs b/src/HelloShop.BasketService/Repositories/DistributedCacheBasketRepository.cs new file mode 100644 index 0000000..135609f --- /dev/null +++ b/src/HelloShop.BasketService/Repositories/DistributedCacheBasketRepository.cs @@ -0,0 +1,28 @@ +// Copyright (c) HelloShop Corporation. All rights reserved. +// See the license file in the project root for more information. + +using HelloShop.BasketService.Entities; +using Microsoft.Extensions.Caching.Distributed; + +namespace HelloShop.BasketService.Repositories +{ + public class DistributedCacheBasketRepository(IDistributedCache cache) : IBasketRepository + { + private const string BasketKeyPrefix = "basket"; + + private static string GetBasketKey(int customerId) => $"{BasketKeyPrefix}:{customerId}"; + + public async Task DeleteBasketAsync(int customerId, CancellationToken token = default) => await cache.RemoveAsync(GetBasketKey(customerId), token); + + public async Task GetBasketAsync(int customerId, CancellationToken token = default) => await cache.GetObjectAsync(GetBasketKey(customerId), token); + + public async Task UpdateBasketAsync(CustomerBasket basket, CancellationToken token = default) + { + DistributedCacheEntryOptions options = new() { SlidingExpiration = TimeSpan.MaxValue }; + + await cache.SetObjectAsync(GetBasketKey(basket.BuyerId), basket, options, token); + + return await GetBasketAsync(basket.BuyerId, token); + } + } +} diff --git a/src/HelloShop.BasketService/Repositories/IBasketRepository.cs b/src/HelloShop.BasketService/Repositories/IBasketRepository.cs new file mode 100644 index 0000000..835a026 --- /dev/null +++ b/src/HelloShop.BasketService/Repositories/IBasketRepository.cs @@ -0,0 +1,16 @@ +// Copyright (c) HelloShop Corporation. All rights reserved. +// See the license file in the project root for more information. + +using HelloShop.BasketService.Entities; + +namespace HelloShop.BasketService.Repositories +{ + public interface IBasketRepository + { + Task GetBasketAsync(int customerId, CancellationToken token = default); + + Task UpdateBasketAsync(CustomerBasket basket, CancellationToken token = default); + + Task DeleteBasketAsync(int customerId, CancellationToken token = default); + } +} diff --git a/src/HelloShop.BasketService/Services/CustomerBasketService.cs b/src/HelloShop.BasketService/Services/CustomerBasketService.cs new file mode 100644 index 0000000..5d7739d --- /dev/null +++ b/src/HelloShop.BasketService/Services/CustomerBasketService.cs @@ -0,0 +1,78 @@ +// Copyright (c) HelloShop Corporation. All rights reserved. +// See the license file in the project root for more information. + +using AutoMapper; +using Google.Protobuf.WellKnownTypes; +using Grpc.Core; +using HelloShop.BasketService.Entities; + + +// Copyright (c) HelloShop Corporation. All rights reserved. +// See the license file in the project root for more information. + +using HelloShop.BasketService.Protos; +using HelloShop.BasketService.Repositories; +using Microsoft.AspNetCore.Authorization; +using System.Security.Claims; + +namespace HelloShop.BasketService.Services +{ + [Authorize] + public class CustomerBasketService(IBasketRepository repository, ILogger logger, IMapper mapper) : Basket.BasketBase + { + public override async Task GetBasket(Empty request, ServerCallContext context) + { + string? nameIdentifier = context.GetHttpContext().User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + + if (!int.TryParse(nameIdentifier, out int userId)) + { + throw new RpcException(new Status(StatusCode.Unauthenticated, "The caller is not authenticated.")); + } + + logger.LogDebug("Begin GetBasketById call from method {Method} for basket id {Id}", context.Method, userId); + + CustomerBasket? basket = await repository.GetBasketAsync(userId, context.CancellationToken); + + if (basket is not null) + { + mapper.Map(basket); + } + + return new(); + } + + public override async Task UpdateBasket(UpdateBasketRequest request, ServerCallContext context) + { + string? nameIdentifier = context.GetHttpContext().User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + + if (!int.TryParse(nameIdentifier, out int userId)) + { + throw new RpcException(new Status(StatusCode.Unauthenticated, "The caller is not authenticated.")); + } + + logger.LogDebug("Begin UpdateBasket call from method {Method} for basket id {Id}", context.Method, userId); + + CustomerBasket customerBasket = mapper.Map(request, opts => opts.AfterMap((src, dest) => dest.BuyerId = userId)); + + CustomerBasket? result = await repository.UpdateBasketAsync(customerBasket, context.CancellationToken); + + result = result ?? throw new RpcException(new Status(StatusCode.NotFound, $"Basket with buyer id {userId} does not exist")); + + return mapper.Map(result); + } + + public override async Task DeleteBasket(Empty request, ServerCallContext context) + { + string? nameIdentifier = context.GetHttpContext().User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + + if (!int.TryParse(nameIdentifier, out int userId)) + { + throw new RpcException(new Status(StatusCode.Unauthenticated, "The caller is not authenticated.")); + } + + await repository.DeleteBasketAsync(userId, context.CancellationToken); + + return new(); + } + } +} diff --git a/src/HelloShop.BasketService/Services/GreeterService.cs b/src/HelloShop.BasketService/Services/GreeterService.cs index 7722524..643df76 100644 --- a/src/HelloShop.BasketService/Services/GreeterService.cs +++ b/src/HelloShop.BasketService/Services/GreeterService.cs @@ -2,17 +2,12 @@ // See the license file in the project root for more information. using Grpc.Core; +using HelloShop.BasketService.Protos; namespace HelloShop.BasketService.Services; public class GreeterService : Greeter.GreeterBase { - private readonly ILogger _logger; - public GreeterService(ILogger logger) - { - _logger = logger; - } - public override Task SayHello(HelloRequest request, ServerCallContext context) { return Task.FromResult(new HelloReply diff --git a/src/HelloShop.BasketService/Validations/Baskets/BasketListItemValidator.cs b/src/HelloShop.BasketService/Validations/Baskets/BasketListItemValidator.cs new file mode 100644 index 0000000..ca5e69a --- /dev/null +++ b/src/HelloShop.BasketService/Validations/Baskets/BasketListItemValidator.cs @@ -0,0 +1,18 @@ +// Copyright (c) HelloShop Corporation. All rights reserved. +// See the license file in the project root for more information. + +using FluentValidation; +using FluentValidation.Validators; +using HelloShop.BasketService.Protos; + +namespace HelloShop.BasketService.Validations.Baskets +{ + public class BasketListItemValidator : AbstractValidator + { + public BasketListItemValidator() + { + RuleFor(x => x.ProductId).GreaterThan(0); + RuleFor(x => x.Quantity).GreaterThan(0); + } + } +} \ No newline at end of file diff --git a/src/HelloShop.BasketService/Validations/Baskets/UpdateBasketRequestValidator.cs b/src/HelloShop.BasketService/Validations/Baskets/UpdateBasketRequestValidator.cs new file mode 100644 index 0000000..9c1839c --- /dev/null +++ b/src/HelloShop.BasketService/Validations/Baskets/UpdateBasketRequestValidator.cs @@ -0,0 +1,17 @@ +// Copyright (c) HelloShop Corporation. All rights reserved. +// See the license file in the project root for more information. + +using FluentValidation; +using HelloShop.BasketService.Protos; + +namespace HelloShop.BasketService.Validations.Baskets +{ + public class UpdateBasketRequestValidator : AbstractValidator + { + public UpdateBasketRequestValidator() + { + RuleFor(x => x.Items).NotNull(); + RuleForEach(x => x.Items).SetValidator(new BasketListItemValidator()); + } + } +} diff --git a/src/HelloShop.BasketService/Validations/Greeters/HelloRequestValidator.cs b/src/HelloShop.BasketService/Validations/Greeters/HelloRequestValidator.cs new file mode 100644 index 0000000..ce3dd3b --- /dev/null +++ b/src/HelloShop.BasketService/Validations/Greeters/HelloRequestValidator.cs @@ -0,0 +1,16 @@ +// Copyright (c) HelloShop Corporation. All rights reserved. +// See the license file in the project root for more information. + +using FluentValidation; +using HelloShop.BasketService.Protos; + +namespace HelloShop.BasketService.Validations.Greeters +{ + public class HelloRequestValidator : AbstractValidator + { + public HelloRequestValidator() + { + RuleFor(x => x.Name).NotNull().NotEmpty().Length(3, 20); + } + } +} diff --git a/src/HelloShop.BasketService/appsettings.json b/src/HelloShop.BasketService/appsettings.json index ec04bc1..f097952 100644 --- a/src/HelloShop.BasketService/appsettings.json +++ b/src/HelloShop.BasketService/appsettings.json @@ -5,5 +5,10 @@ "Microsoft.AspNetCore": "Warning" } }, + "Kestrel": { + "EndpointDefaults": { + "Protocols": "Http2" + } + }, "AllowedHosts": "*" } \ No newline at end of file diff --git a/src/HelloShop.HybridApp/HelloShop.HybridApp.csproj b/src/HelloShop.HybridApp/HelloShop.HybridApp.csproj index 5659c69..63578c4 100644 --- a/src/HelloShop.HybridApp/HelloShop.HybridApp.csproj +++ b/src/HelloShop.HybridApp/HelloShop.HybridApp.csproj @@ -58,9 +58,9 @@ - - - + + + diff --git a/src/HelloShop.IdentityService/HelloShop.IdentityService.csproj b/src/HelloShop.IdentityService/HelloShop.IdentityService.csproj index a8605af..b610f84 100644 --- a/src/HelloShop.IdentityService/HelloShop.IdentityService.csproj +++ b/src/HelloShop.IdentityService/HelloShop.IdentityService.csproj @@ -8,9 +8,9 @@ - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/HelloShop.ProductService/HelloShop.ProductService.csproj b/src/HelloShop.ProductService/HelloShop.ProductService.csproj index 7a3fbcb..af10394 100644 --- a/src/HelloShop.ProductService/HelloShop.ProductService.csproj +++ b/src/HelloShop.ProductService/HelloShop.ProductService.csproj @@ -8,8 +8,8 @@ - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/HelloShop.ServiceDefaults/Extensions/OpenApiExtensions.cs b/src/HelloShop.ServiceDefaults/Extensions/OpenApiExtensions.cs index 7d5ab3f..df7eb41 100644 --- a/src/HelloShop.ServiceDefaults/Extensions/OpenApiExtensions.cs +++ b/src/HelloShop.ServiceDefaults/Extensions/OpenApiExtensions.cs @@ -22,7 +22,7 @@ namespace HelloShop.ServiceDefaults.Extensions services.Configure(options => { - options.DocumentTitle = Assembly.GetExecutingAssembly().GetName().Name; + options.DocumentTitle = Assembly.GetEntryAssembly()?.GetName().Name; options.InjectStylesheet("/ServiceDefaults/Resources/OpenApi/Custom.css"); }); diff --git a/src/HelloShop.ServiceDefaults/HelloShop.ServiceDefaults.csproj b/src/HelloShop.ServiceDefaults/HelloShop.ServiceDefaults.csproj index 033d269..0c30ba5 100644 --- a/src/HelloShop.ServiceDefaults/HelloShop.ServiceDefaults.csproj +++ b/src/HelloShop.ServiceDefaults/HelloShop.ServiceDefaults.csproj @@ -7,8 +7,8 @@ - - + + diff --git a/tests/HelloShop.FunctionalTests/HelloShop.FunctionalTests.csproj b/tests/HelloShop.FunctionalTests/HelloShop.FunctionalTests.csproj index 2706853..5396ee6 100644 --- a/tests/HelloShop.FunctionalTests/HelloShop.FunctionalTests.csproj +++ b/tests/HelloShop.FunctionalTests/HelloShop.FunctionalTests.csproj @@ -9,14 +9,14 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/HelloShop.ProductService.FunctionalTests/BrandApiIntegrationTest.cs b/tests/HelloShop.ProductService.FunctionalTests/BrandApiIntegrationTest.cs index 5e53675..794f8db 100644 --- a/tests/HelloShop.ProductService.FunctionalTests/BrandApiIntegrationTest.cs +++ b/tests/HelloShop.ProductService.FunctionalTests/BrandApiIntegrationTest.cs @@ -21,7 +21,6 @@ namespace HelloShop.ProductService.FunctionalTests // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); - } [Fact] diff --git a/tests/HelloShop.ProductService.FunctionalTests/HelloShop.ProductService.FunctionalTests.csproj b/tests/HelloShop.ProductService.FunctionalTests/HelloShop.ProductService.FunctionalTests.csproj index 6489c44..b1e92fe 100644 --- a/tests/HelloShop.ProductService.FunctionalTests/HelloShop.ProductService.FunctionalTests.csproj +++ b/tests/HelloShop.ProductService.FunctionalTests/HelloShop.ProductService.FunctionalTests.csproj @@ -14,11 +14,11 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/HelloShop.ProductService.UnitTests/HelloShop.ProductService.UnitTests.csproj b/tests/HelloShop.ProductService.UnitTests/HelloShop.ProductService.UnitTests.csproj index d3cf251..1e39637 100644 --- a/tests/HelloShop.ProductService.UnitTests/HelloShop.ProductService.UnitTests.csproj +++ b/tests/HelloShop.ProductService.UnitTests/HelloShop.ProductService.UnitTests.csproj @@ -14,11 +14,11 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive