diff --git a/apps/auth-server/src/EShopOnAbp.AuthServer/AnonymousUserItem.cs b/apps/auth-server/src/EShopOnAbp.AuthServer/AnonymousUserItem.cs new file mode 100644 index 00000000..3679543d --- /dev/null +++ b/apps/auth-server/src/EShopOnAbp.AuthServer/AnonymousUserItem.cs @@ -0,0 +1,6 @@ +namespace EShopOnAbp.AuthServer; + +public class AnonymousUserItem +{ + public string AnonymousUserId { get; set; } +} \ No newline at end of file diff --git a/apps/auth-server/src/EShopOnAbp.AuthServer/EShopOnAbpAuthServerModule.cs b/apps/auth-server/src/EShopOnAbp.AuthServer/EShopOnAbpAuthServerModule.cs index 5110d159..4e4897e4 100644 --- a/apps/auth-server/src/EShopOnAbp.AuthServer/EShopOnAbpAuthServerModule.cs +++ b/apps/auth-server/src/EShopOnAbp.AuthServer/EShopOnAbpAuthServerModule.cs @@ -99,6 +99,16 @@ namespace EShopOnAbp.AuthServer options.IsEnabled = true; }); + // context.Services.Replace(ServiceDescriptor.Transient()); + + + Configure(options => + { + options.RequestedClaims.AddRange(new[]{ + EShopConstants.AnonymousUserClaimName + }); + }); + Configure(options => { options.ApplicationName = "AuthServer"; diff --git a/apps/auth-server/src/EShopOnAbp.AuthServer/EShopUserClaimsPrincipalFactory.cs b/apps/auth-server/src/EShopOnAbp.AuthServer/EShopUserClaimsPrincipalFactory.cs new file mode 100644 index 00000000..32d0e1fc --- /dev/null +++ b/apps/auth-server/src/EShopOnAbp.AuthServer/EShopUserClaimsPrincipalFactory.cs @@ -0,0 +1,69 @@ +using System; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using EShopOnAbp.Shared.Hosting.AspNetCore; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Volo.Abp.Caching; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Identity; +using Volo.Abp.Security.Claims; +using IdentityRole = Volo.Abp.Identity.IdentityRole; +using IdentityUser = Volo.Abp.Identity.IdentityUser; + +namespace EShopOnAbp.AuthServer; + +[Dependency(ReplaceServices = true)] +[ExposeServices(typeof(AbpUserClaimsPrincipalFactory))] +public class EShopUserClaimsPrincipalFactory : AbpUserClaimsPrincipalFactory +{ + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly ILogger _logger; + private readonly IDistributedCache _cache; + protected HttpContext HttpContext => _httpContextAccessor.HttpContext; + + public EShopUserClaimsPrincipalFactory( + UserManager userManager, + RoleManager roleManager, + IOptions options, + ICurrentPrincipalAccessor currentPrincipalAccessor, + IAbpClaimsPrincipalFactory abpClaimsPrincipalFactory, + IHttpContextAccessor httpContextAccessor, + ILogger logger, + IDistributedCache cache) : base(userManager, + roleManager, + options, + currentPrincipalAccessor, + abpClaimsPrincipalFactory) + { + _httpContextAccessor = httpContextAccessor; + _logger = logger; + _cache = cache; + } + + public override async Task CreateAsync(IdentityUser user) + { + var principal = await base.CreateAsync(user); + if (HttpContext.Request.Cookies.ContainsKey(EShopConstants.AnonymousUserClaimName)) + { + HttpContext.Request.Cookies.TryGetValue(EShopConstants.AnonymousUserClaimName, out var anonymousUserId); + + await _cache.SetAsync(user.Id, new AnonymousUserItem {AnonymousUserId = anonymousUserId}, options: new DistributedCacheEntryOptions() + { + AbsoluteExpiration = DateTimeOffset.UtcNow.AddSeconds(60) + }); + _logger.LogInformation($"Cached anonymousUserId:{anonymousUserId}."); + } + + var cachedItem = await _cache.GetAsync(user.Id); + _logger.LogInformation($"Retrieved anonymousUserId:{cachedItem.AnonymousUserId} from cache."); + principal.Identities.First() + .AddClaim(new Claim(EShopConstants.AnonymousUserClaimName, cachedItem.AnonymousUserId)); + + return principal; + } +} \ No newline at end of file diff --git a/apps/public-web/src/EShopOnAbp.PublicWeb/Basket/UserBasketProvider.cs b/apps/public-web/src/EShopOnAbp.PublicWeb/Basket/UserBasketProvider.cs index 2fd7e5de..be4171d5 100644 --- a/apps/public-web/src/EShopOnAbp.PublicWeb/Basket/UserBasketProvider.cs +++ b/apps/public-web/src/EShopOnAbp.PublicWeb/Basket/UserBasketProvider.cs @@ -1,12 +1,11 @@ -using Castle.Core.Logging; -using EShopOnAbp.BasketService; +using EShopOnAbp.BasketService; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; +using EShopOnAbp.Shared.Hosting.AspNetCore; +using Microsoft.AspNetCore.Authentication; using Volo.Abp.DependencyInjection; using Volo.Abp.Guids; using Volo.Abp.Users; @@ -41,8 +40,13 @@ namespace EShopOnAbp.PublicWeb.Basket { try { + var access_token = await HttpContext.GetTokenAsync("access_token"); + logger.LogInformation($"ACCESS_TOKEN:{access_token}"); + logger.LogInformation($"*********************"); + var id_token = await HttpContext.GetTokenAsync("id_token"); + logger.LogInformation($"ID_TOKEN:{id_token}"); // Get anonymous user id from cookie - HttpContext.Request.Cookies.TryGetValue(CookieConstants.AnonymousUserCookieName, + HttpContext.Request.Cookies.TryGetValue(EShopConstants.AnonymousUserClaimName, out string anonymousUserId); logger.LogInformation($"========= Anonymous User Id from Cookie:{anonymousUserId} ========= "); @@ -50,8 +54,14 @@ namespace EShopOnAbp.PublicWeb.Basket if (string.IsNullOrEmpty(anonymousUserId)) { anonymousUserId = guidGenerator.Create().ToString(); - HttpContext.Response.Cookies.Append(CookieConstants.AnonymousUserCookieName, anonymousUserId); - logger.LogInformation($"========= Generated new User Id:{anonymousUserId} ========= APPENDED TO COOKIE ====== "); + HttpContext.Response.Cookies.Append(EShopConstants.AnonymousUserClaimName, anonymousUserId + // , new CookieOptions + // { + // SameSite = SameSiteMode.Lax + // } + ); + logger.LogInformation( + $"========= Generated new User Id:{anonymousUserId} ========= APPENDED TO COOKIE ====== "); } if (!currentUser.IsAuthenticated) @@ -60,11 +70,20 @@ namespace EShopOnAbp.PublicWeb.Basket $"========= Get Basket for Anonymous UserId:{anonymousUserId} ========= APPENDED TO COOKIE ====== "); return await basketAppService.GetByAnonymousUserIdAsync(Guid.Parse(anonymousUserId)); } - else + + //TODO: Merge with anonymously stored cart if exist + var userClaimValue = currentUser.FindClaimValue(EShopConstants.AnonymousUserClaimName); + foreach (var claim in currentUser.GetAllClaims()) + { + logger.LogInformation($"Claim Type:{claim.Type}-Value:{claim.Value}"); + } + + if (string.IsNullOrEmpty(userClaimValue)) { - //TODO: Merge with anonymously stored cart if exist return await basketAppService.GetAsync(); } + + return await basketAppService.MergeBasketsAsync(); } catch (Exception ex) { @@ -73,4 +92,4 @@ namespace EShopOnAbp.PublicWeb.Basket } } } -} +} \ No newline at end of file diff --git a/apps/public-web/src/EShopOnAbp.PublicWeb/Components/Basket/Default.cshtml b/apps/public-web/src/EShopOnAbp.PublicWeb/Components/Basket/Default.cshtml index e0057a76..05bbe1da 100644 --- a/apps/public-web/src/EShopOnAbp.PublicWeb/Components/Basket/Default.cshtml +++ b/apps/public-web/src/EShopOnAbp.PublicWeb/Components/Basket/Default.cshtml @@ -1,4 +1,3 @@ - @using EShopOnAbp.Localization @using Microsoft.Extensions.Localization @@ -10,38 +9,50 @@ @if (!Model.Items.Any()) {
-

Your basket is empty. Click to a product to add it!

+

+ + Your basket is empty. Click to a product to add it! + +

} else { -
- @foreach (var item in Model.Items) - { -
- @if (!item.ImageName.IsNullOrEmpty()) - { - - } - else +
+
+
+ @foreach (var item in Model.Items) { - +
+ @if (!item.ImageName.IsNullOrEmpty()) + { + + } + else + { + + } + @item.ProductName + @if (item.Count > 1) + { + (@("x" + item.Count)) + } + - $@item.TotalPrice.ToString("0.00") + +
} - @item.ProductName - @if (item.Count > 1) - { - (@("x" + item.Count)) - } - - $@item.TotalPrice.ToString("0.00") - +
+
+ @L["Total"]: $@Model.TotalPrice.ToString("0.00") +
- } -
-
- @L["Total"]: $@Model.TotalPrice.ToString("0.00")
-
- +
+

Total Selected (@Model.Items.Count)

+

$@Model.TotalPrice.ToString("0.00")

+
+ +
} diff --git a/apps/public-web/src/EShopOnAbp.PublicWeb/CookieConstants.cs b/apps/public-web/src/EShopOnAbp.PublicWeb/CookieConstants.cs deleted file mode 100644 index 1994218b..00000000 --- a/apps/public-web/src/EShopOnAbp.PublicWeb/CookieConstants.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System; - -namespace EShopOnAbp.PublicWeb; - -public static class CookieConstants -{ - public const string AnonymousUserCookieName = "eshop_anonymousId"; -} \ No newline at end of file diff --git a/apps/public-web/src/EShopOnAbp.PublicWeb/Pages/Basket.cshtml b/apps/public-web/src/EShopOnAbp.PublicWeb/Pages/Basket.cshtml index e30d044d..4545fae1 100644 --- a/apps/public-web/src/EShopOnAbp.PublicWeb/Pages/Basket.cshtml +++ b/apps/public-web/src/EShopOnAbp.PublicWeb/Pages/Basket.cshtml @@ -5,15 +5,16 @@ @model EShopOnAbp.PublicWeb.Pages.Basket @section scripts { - - + + } @inject IStringLocalizer L -
+

@L["YourBasket"]

@await Component.InvokeAsync(typeof(BasketWidgetViewComponent))
+
\ No newline at end of file diff --git a/apps/public-web/src/EShopOnAbp.PublicWeb/Pages/index.js b/apps/public-web/src/EShopOnAbp.PublicWeb/Pages/index.js index 9af51771..04c54ef9 100644 --- a/apps/public-web/src/EShopOnAbp.PublicWeb/Pages/index.js +++ b/apps/public-web/src/EShopOnAbp.PublicWeb/Pages/index.js @@ -1,6 +1,6 @@ (function () { //set anonymous user - let anonymousId = abp.utils.getCookieValue("eshop_anonymousId"); + let anonymousId = abp.utils.getCookieValue("anonymous_id"); if (anonymousId != null) { console.info("Anonymous user set:" + anonymousId); } diff --git a/apps/public-web/src/EShopOnAbp.PublicWeb/wwwroot/client-proxies/basket-proxy.js b/apps/public-web/src/EShopOnAbp.PublicWeb/wwwroot/client-proxies/basket-proxy.js index de10ce04..eedba967 100644 --- a/apps/public-web/src/EShopOnAbp.PublicWeb/wwwroot/client-proxies/basket-proxy.js +++ b/apps/public-web/src/EShopOnAbp.PublicWeb/wwwroot/client-proxies/basket-proxy.js @@ -25,6 +25,13 @@ }, ajaxParams)); }; + eShopOnAbp.basketService.basket.mergeBaskets = function(ajaxParams) { + return abp.ajax($.extend(true, { + url: abp.appPath + 'api/basket/basket/merge-baskets', + type: 'POST' + }, ajaxParams)); + }; + eShopOnAbp.basketService.basket.addProduct = function(input, ajaxParams) { return abp.ajax($.extend(true, { url: abp.appPath + 'api/basket/basket/product', diff --git a/apps/public-web/src/EShopOnAbp.PublicWeb/wwwroot/components/basket-widget.js b/apps/public-web/src/EShopOnAbp.PublicWeb/wwwroot/components/basket-widget.js index c71356ba..40abc2e9 100644 --- a/apps/public-web/src/EShopOnAbp.PublicWeb/wwwroot/components/basket-widget.js +++ b/apps/public-web/src/EShopOnAbp.PublicWeb/wwwroot/components/basket-widget.js @@ -1,6 +1,6 @@ (function () { - let anonymousId = abp.utils.getCookieValue("eshop_anonymousId"); + let anonymousId = abp.utils.getCookieValue("anonymous_id"); abp.widgets.BasketWidget = function ($wrapper) { diff --git a/services/basket/src/EShopOnAbp.BasketService.Application.Contracts/IBasketAppService.cs b/services/basket/src/EShopOnAbp.BasketService.Application.Contracts/IBasketAppService.cs index 55a4ba0a..2bf27fe7 100644 --- a/services/basket/src/EShopOnAbp.BasketService.Application.Contracts/IBasketAppService.cs +++ b/services/basket/src/EShopOnAbp.BasketService.Application.Contracts/IBasketAppService.cs @@ -8,6 +8,7 @@ public interface IBasketAppService : IApplicationService { Task GetAsync(); Task GetByAnonymousUserIdAsync(Guid id); + Task MergeBasketsAsync(); Task AddProductAsync(AddProductDto input); Task RemoveProductAsync(RemoveProductDto input); Task PurchaseAsync(); diff --git a/services/basket/src/EShopOnAbp.BasketService.Application/BasketAppService.cs b/services/basket/src/EShopOnAbp.BasketService.Application/BasketAppService.cs index 2aab9990..f2280018 100644 --- a/services/basket/src/EShopOnAbp.BasketService.Application/BasketAppService.cs +++ b/services/basket/src/EShopOnAbp.BasketService.Application/BasketAppService.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Volo.Abp.Users; using EShopOnAbp.CatalogService.Products; +using Microsoft.Extensions.Logging; using Volo.Abp; using Volo.Abp.EventBus.Distributed; @@ -15,7 +16,6 @@ public class BasketAppService : BasketServiceAppService, IBasketAppService private readonly IBasketRepository _basketRepository; private readonly IBasketProductService _basketProductService; private readonly IDistributedEventBus _distributedEventBus; - private Guid _anonymousUserId { get; set; } public BasketAppService( IBasketRepository basketRepository, @@ -36,14 +36,43 @@ public class BasketAppService : BasketServiceAppService, IBasketAppService public async Task GetByAnonymousUserIdAsync(Guid id) { var basket = await _basketRepository.GetAsync(id); - _anonymousUserId = id; + ; return await GetBasketDtoAsync(basket); } - public async Task AddProductAsync(AddProductDto input) + public async Task MergeBasketsAsync() { - Console.WriteLine("========================= AnonymousId: "+input.AnonymousId); + //TODO: move to custom shared project + var anonymousUserIdString = CurrentUser.FindClaimValue("anonymous_id"); + + if (!Guid.TryParse(anonymousUserIdString, out Guid anonymousUserId)) + { + Logger.LogError($"Couldn't parse anonymous Id from claim!{anonymousUserIdString}"); + } + + Basket anonymousUserBasket = await _basketRepository.GetAsync(anonymousUserId); + if (!CurrentUser.IsAuthenticated) + { + Logger.LogWarning($"User is not authenticated! Merging baskets failed!"); + return await GetBasketDtoAsync(anonymousUserBasket); + } + var userBasket = await _basketRepository.GetAsync(CurrentUser.GetId()); + + foreach (var item in anonymousUserBasket.Items) + { + userBasket.AddProduct(item.ProductId, item.Count); + } + + await _basketRepository.UpdateAsync(userBasket); + anonymousUserBasket.Clear(); + await _basketRepository.UpdateAsync(anonymousUserBasket); + + return await GetBasketDtoAsync(userBasket); + } + + public async Task AddProductAsync(AddProductDto input) + { Guid userId = CurrentUser.IsAuthenticated ? CurrentUser.GetId() : input.AnonymousId.GetValueOrDefault(); var basket = await _basketRepository.GetAsync(userId); diff --git a/services/basket/src/EShopOnAbp.BasketService.HttpApi.Client/ClientProxies/BasketClientProxy.Generated.cs b/services/basket/src/EShopOnAbp.BasketService.HttpApi.Client/ClientProxies/BasketClientProxy.Generated.cs index 6a723592..b8174b87 100644 --- a/services/basket/src/EShopOnAbp.BasketService.HttpApi.Client/ClientProxies/BasketClientProxy.Generated.cs +++ b/services/basket/src/EShopOnAbp.BasketService.HttpApi.Client/ClientProxies/BasketClientProxy.Generated.cs @@ -28,6 +28,11 @@ namespace EShopOnAbp.BasketService.ClientProxies }); } + public virtual async Task MergeBasketsAsync() + { + return await RequestAsync(nameof(MergeBasketsAsync)); + } + public virtual async Task AddProductAsync(AddProductDto input) { return await RequestAsync(nameof(AddProductAsync), new ClientProxyRequestTypeValue diff --git a/services/basket/src/EShopOnAbp.BasketService.HttpApi.Client/ClientProxies/basket-generate-proxy.json b/services/basket/src/EShopOnAbp.BasketService.HttpApi.Client/ClientProxies/basket-generate-proxy.json index 34f02bdc..964d0384 100644 --- a/services/basket/src/EShopOnAbp.BasketService.HttpApi.Client/ClientProxies/basket-generate-proxy.json +++ b/services/basket/src/EShopOnAbp.BasketService.HttpApi.Client/ClientProxies/basket-generate-proxy.json @@ -75,6 +75,21 @@ "allowAnonymous": null, "implementFrom": "EShopOnAbp.BasketService.IBasketAppService" }, + "MergeBasketsAsync": { + "uniqueName": "MergeBasketsAsync", + "name": "MergeBasketsAsync", + "httpMethod": "POST", + "url": "api/basket/basket/merge-baskets", + "supportedVersions": [], + "parametersOnMethod": [], + "parameters": [], + "returnValue": { + "type": "EShopOnAbp.BasketService.BasketDto", + "typeSimple": "EShopOnAbp.BasketService.BasketDto" + }, + "allowAnonymous": null, + "implementFrom": "EShopOnAbp.BasketService.IBasketAppService" + }, "AddProductAsyncByInput": { "uniqueName": "AddProductAsyncByInput", "name": "AddProductAsync", diff --git a/shared/EShopOnAbp.Shared.Hosting.AspNetCore/EShopConstants.cs b/shared/EShopOnAbp.Shared.Hosting.AspNetCore/EShopConstants.cs new file mode 100644 index 00000000..2e023cea --- /dev/null +++ b/shared/EShopOnAbp.Shared.Hosting.AspNetCore/EShopConstants.cs @@ -0,0 +1,7 @@ +namespace EShopOnAbp.Shared.Hosting.AspNetCore; + +public static class EShopConstants +{ + // public const string AnonymousUserCookieName = "eshop_anonymousId"; + public const string AnonymousUserClaimName = "anonymous_id"; +} \ No newline at end of file