diff --git a/Nop.Plugin.Misc.AuctionPlugin/Areas/Admin/Controllers/AnnouncementController.cs b/Nop.Plugin.Misc.AuctionPlugin/Areas/Admin/Controllers/AnnouncementController.cs index 102a2fc..07400ef 100644 --- a/Nop.Plugin.Misc.AuctionPlugin/Areas/Admin/Controllers/AnnouncementController.cs +++ b/Nop.Plugin.Misc.AuctionPlugin/Areas/Admin/Controllers/AnnouncementController.cs @@ -118,8 +118,8 @@ namespace Nop.Plugin.Misc.AuctionPlugin.Areas.Admin.Controllers Data = new BidNotificationMessage { ProductName = viewModel.ProductName, - BidPrice = viewModel.BidPrice, - NextStepAmount = viewModel.NextStepAmount + BidPrice = viewModel.BidPrice.ToString(), + NextStepAmount = viewModel.NextStepAmount.ToString() } }; var jsonMessage = JsonConvert.SerializeObject(bid, Formatting.Indented, diff --git a/Nop.Plugin.Misc.AuctionPlugin/Areas/Admin/Models/BidNotificationViewModel.cs b/Nop.Plugin.Misc.AuctionPlugin/Areas/Admin/Models/BidNotificationViewModel.cs index fdad9ab..11c59a8 100644 --- a/Nop.Plugin.Misc.AuctionPlugin/Areas/Admin/Models/BidNotificationViewModel.cs +++ b/Nop.Plugin.Misc.AuctionPlugin/Areas/Admin/Models/BidNotificationViewModel.cs @@ -8,9 +8,9 @@ namespace Nop.Plugin.Misc.AuctionPlugin.Areas.Admin.Models [NopResourceDisplayName("Name")] public string ProductName { get; set; } - public int BidPrice { get; set; } + public decimal BidPrice { get; set; } - public int NextStepAmount { get; set; } + public decimal NextStepAmount { get; set; } } diff --git a/Nop.Plugin.Misc.AuctionPlugin/Components/AuctionPublicViewComponent.cs b/Nop.Plugin.Misc.AuctionPlugin/Components/AuctionPublicViewComponent.cs index 4bdd9dd..8c5adf5 100644 --- a/Nop.Plugin.Misc.AuctionPlugin/Components/AuctionPublicViewComponent.cs +++ b/Nop.Plugin.Misc.AuctionPlugin/Components/AuctionPublicViewComponent.cs @@ -1,6 +1,9 @@ using Microsoft.AspNetCore.Mvc; using Nop.Core; +using Nop.Plugin.Misc.AuctionPlugin.Domains.Dtos; +using Nop.Plugin.Misc.AuctionPlugin.Domains.Entities; using Nop.Plugin.Misc.AuctionPlugin.Models; +using Nop.Plugin.Misc.AuctionPlugin.Services; using Nop.Services.Cms; using Nop.Services.Common; using Nop.Services.Logging; @@ -18,6 +21,7 @@ public class AuctionPublicViewComponent : NopViewComponent protected readonly IGenericAttributeService _genericAttributeService; protected readonly IWidgetPluginManager _widgetPluginManager; protected readonly IWorkContext _workContext; + protected readonly AuctionService _auctionService; protected readonly AuctionSettings _auctionSettings; protected readonly ILogger _logger; @@ -29,6 +33,7 @@ public class AuctionPublicViewComponent : NopViewComponent IGenericAttributeService genericAttributeService, IWidgetPluginManager widgetPluginManager, IWorkContext workContext, + AuctionService auctionService, AuctionSettings auctionSettings, ILogger logger) { @@ -36,6 +41,7 @@ public class AuctionPublicViewComponent : NopViewComponent _genericAttributeService = genericAttributeService; _widgetPluginManager = widgetPluginManager; _workContext = workContext; + _auctionService = auctionService; _auctionSettings = auctionSettings; _logger = logger; } @@ -89,9 +95,42 @@ public class AuctionPublicViewComponent : NopViewComponent } await _logger.InformationAsync("WidgetViewComponent called II"); + + //is it under Auction? + + List auctionIds = await _auctionService.GetProductToAuctionsByProductIdAsync(productDetailsModel.Id); + int auctionId = 0; + if(auctionIds == null || auctionIds.Count == 0) + { + return Content(string.Empty); + } + + if (auctionIds.Count > 1) + { + List openAuctionList = []; + + //we have more auctions so we return the closest open + foreach (var auctionMapping in auctionIds) + { + var auction = await _auctionService.GetAuctionDtoByIdAsync(auctionMapping.AuctionId); + if (!auction.Closed) + { + openAuctionList.Add(auction); + } + } + //first auction that started or strats not earlier than yesterday + auctionId = openAuctionList.Where(x => x.StartDateUtc > DateTime.UtcNow.AddDays(-1)).OrderBy(x => x.StartDateUtc).FirstOrDefault().Id; + } + else + { + var valami = await _auctionService.GetAuctionDtoByIdAsync(auctionIds.FirstOrDefault().AuctionId); + auctionId = valami.Id; + } + productBidBoxViewModel.WidgetZone = widgetZone; productBidBoxViewModel.BasePrice = productDetailsModel.ProductPrice.OldPriceValue; productBidBoxViewModel.CurrentPrice = productDetailsModel.ProductPrice.PriceValue; + productBidBoxViewModel.AuctionId = auctionId; productBidBoxViewModel.CustomerId = customer.Id; productBidBoxViewModel.ProductId = productDetailsModel.Id; productBidBoxViewModel.LicitStep = 50000; //add calculation diff --git a/Nop.Plugin.Misc.AuctionPlugin/Content/Js/LiveAnnouncement.js b/Nop.Plugin.Misc.AuctionPlugin/Content/Js/LiveAnnouncement.js index 3f127f2..40a3eef 100644 --- a/Nop.Plugin.Misc.AuctionPlugin/Content/Js/LiveAnnouncement.js +++ b/Nop.Plugin.Misc.AuctionPlugin/Content/Js/LiveAnnouncement.js @@ -19,15 +19,30 @@ $(function () { }, 100000); }); } - + connection.onclose(function () { start(); }); + window.sendMessageToServer = function (messageType, data) { + var messageWrapper = { + MessageType: messageType, + Data: data + }; + + connection.invoke("ReceiveMessageFromClient", messageWrapper) + .then(() => { + console.log("Message successfully sent to the server."); + }) + .catch(err => { + console.error("Error sending message to the server:", err); + }); + }; + start(); }); - +// Function to send a message to the server diff --git a/Nop.Plugin.Misc.AuctionPlugin/Domains/DataLayer/AuctionDbTable.cs b/Nop.Plugin.Misc.AuctionPlugin/Domains/DataLayer/AuctionDbTable.cs index 3b3093b..7400e82 100644 --- a/Nop.Plugin.Misc.AuctionPlugin/Domains/DataLayer/AuctionDbTable.cs +++ b/Nop.Plugin.Misc.AuctionPlugin/Domains/DataLayer/AuctionDbTable.cs @@ -18,4 +18,5 @@ public class AuctionDbTable: MgDbTableBase { return GetAllAsync(auctions => auctions.OrderByDescending(x => x.StartDateUtc), _ => default); } + } \ No newline at end of file diff --git a/Nop.Plugin.Misc.AuctionPlugin/Domains/DataLayer/ProductToAuctionDbTable.cs b/Nop.Plugin.Misc.AuctionPlugin/Domains/DataLayer/ProductToAuctionDbTable.cs index 674a15b..0b6f864 100644 --- a/Nop.Plugin.Misc.AuctionPlugin/Domains/DataLayer/ProductToAuctionDbTable.cs +++ b/Nop.Plugin.Misc.AuctionPlugin/Domains/DataLayer/ProductToAuctionDbTable.cs @@ -17,4 +17,9 @@ public class ProductToAuctionDbTable: MgDbTableBase { return Table.Where(x => x.AuctionId == auctionId && x.ProductId == productId); } + + public IQueryable GetByProductId(int productId) + { + return Table.Where(x => x.ProductId == productId); + } } \ No newline at end of file diff --git a/Nop.Plugin.Misc.AuctionPlugin/Domains/Dtos/AuctionBidDto.cs b/Nop.Plugin.Misc.AuctionPlugin/Domains/Dtos/AuctionBidDto.cs index 76e7cbf..3d5862d 100644 --- a/Nop.Plugin.Misc.AuctionPlugin/Domains/Dtos/AuctionBidDto.cs +++ b/Nop.Plugin.Misc.AuctionPlugin/Domains/Dtos/AuctionBidDto.cs @@ -10,7 +10,7 @@ public class AuctionBidDto : IAuctionBidDto public int CustomerId { get; set; } public int ProductId { get; set; } public bool IsWinner { get; set; } - public int BidPrice { get; set; } + public decimal BidPrice { get; set; } public AuctionBidDto() diff --git a/Nop.Plugin.Misc.AuctionPlugin/Domains/Dtos/Interfaces/IAuctionBidDtoBase.cs b/Nop.Plugin.Misc.AuctionPlugin/Domains/Dtos/Interfaces/IAuctionBidDtoBase.cs index e978195..1bc4f50 100644 --- a/Nop.Plugin.Misc.AuctionPlugin/Domains/Dtos/Interfaces/IAuctionBidDtoBase.cs +++ b/Nop.Plugin.Misc.AuctionPlugin/Domains/Dtos/Interfaces/IAuctionBidDtoBase.cs @@ -11,5 +11,5 @@ public interface IAuctionBidDtoBase : IMgModelDtoBase public int ProductId { get; set; } public bool IsWinner { get; set; } - public int BidPrice { get; set; } + public decimal BidPrice { get; set; } } \ No newline at end of file diff --git a/Nop.Plugin.Misc.AuctionPlugin/Domains/Entities/AuctionBid.cs b/Nop.Plugin.Misc.AuctionPlugin/Domains/Entities/AuctionBid.cs index 1028668..9695fd8 100644 --- a/Nop.Plugin.Misc.AuctionPlugin/Domains/Entities/AuctionBid.cs +++ b/Nop.Plugin.Misc.AuctionPlugin/Domains/Entities/AuctionBid.cs @@ -15,7 +15,7 @@ namespace Nop.Plugin.Misc.AuctionPlugin.Domains.Entities public int ProductId { get; set; } public bool IsWinner { get; set; } - public int BidPrice { get; set; } + public decimal BidPrice { get; set; } [SkipValuesOnUpdate] public DateTime Created { get; set; } diff --git a/Nop.Plugin.Misc.AuctionPlugin/Hubs/AuctionHub.cs b/Nop.Plugin.Misc.AuctionPlugin/Hubs/AuctionHub.cs index 2a0ba7d..14a2624 100644 --- a/Nop.Plugin.Misc.AuctionPlugin/Hubs/AuctionHub.cs +++ b/Nop.Plugin.Misc.AuctionPlugin/Hubs/AuctionHub.cs @@ -3,18 +3,30 @@ using Microsoft.AspNetCore.SignalR; using System.Threading.Tasks; using Nop.Services.Logging; +using Nop.Plugin.Misc.AuctionPlugin.Hubs.Messages; +using Nop.Plugin.Misc.AuctionPlugin.Models; +using Newtonsoft.Json.Serialization; +using Newtonsoft.Json; +using Nop.Web.Models.Media; +using Nop.Services.Catalog; +using Nop.Plugin.Misc.AuctionPlugin.Services; +using Nop.Plugin.Misc.AuctionPlugin.Domains.Entities; +using Newtonsoft.Json.Linq; namespace Nop.Plugin.Misc.AuctionPlugin.Hubs { public class AuctionHub : Hub - { + { + protected readonly SignalRMessageHandler _signalRMessageHandler; + ILogger _logger; //HubCallerContext _hubCallerContext; - public AuctionHub(ILogger logger) + public AuctionHub(ILogger logger, SignalRMessageHandler signalRMessageHandler) { - _logger = logger; + _logger = logger; + _signalRMessageHandler = signalRMessageHandler; } public override async Task OnConnectedAsync() @@ -32,7 +44,7 @@ namespace Nop.Plugin.Misc.AuctionPlugin.Hubs } - public async Task ReceiveMessageFromClient(string message) + public async Task ReceiveRegularMessageFromClient(string message) { await _logger.InformationAsync(message); // Broadcast the message received from the client to all clients @@ -41,16 +53,14 @@ namespace Nop.Plugin.Misc.AuctionPlugin.Hubs //await _signalRservice.TestHub(); } - //public async Task Send(string announcement) - //{ - // await _logger.InformationAsync($" Hub Send method called with messgae: {announcement}"); - // await Clients.All.SendAsync("send", announcement); - //} - - public async Task SendPriceToUsers(string message) + public async Task ReceiveMessageFromClient(MessageWrapper message) { - await Clients.All.SendAsync("SendPrice", message); + // Log the message type and data + await _logger.InformationAsync($"Received message of type: {message.MessageType}"); + await _signalRMessageHandler.HandleMessage(message); + } + } } diff --git a/Nop.Plugin.Misc.AuctionPlugin/Hubs/Messages/BidNotificationMessage.cs b/Nop.Plugin.Misc.AuctionPlugin/Hubs/Messages/BidNotificationMessage.cs index d5fbcec..da733f3 100644 --- a/Nop.Plugin.Misc.AuctionPlugin/Hubs/Messages/BidNotificationMessage.cs +++ b/Nop.Plugin.Misc.AuctionPlugin/Hubs/Messages/BidNotificationMessage.cs @@ -9,7 +9,7 @@ namespace Nop.Plugin.Misc.AuctionPlugin.Hubs.Messages public class BidNotificationMessage { public string ProductName { get; set; } - public int BidPrice { get; set; } - public int NextStepAmount { get; set; } + public string BidPrice { get; set; } + public string NextStepAmount { get; set; } } } diff --git a/Nop.Plugin.Misc.AuctionPlugin/Hubs/SignalRMessageHandler.cs b/Nop.Plugin.Misc.AuctionPlugin/Hubs/SignalRMessageHandler.cs new file mode 100644 index 0000000..585751d --- /dev/null +++ b/Nop.Plugin.Misc.AuctionPlugin/Hubs/SignalRMessageHandler.cs @@ -0,0 +1,125 @@ +using Nop.Plugin.Misc.AuctionPlugin.Hubs.Messages; +using Newtonsoft.Json.Linq; +using Nop.Plugin.Misc.AuctionPlugin.Models; +using Nop.Plugin.Misc.AuctionPlugin.Services; +using Nop.Services.Catalog; +using Newtonsoft.Json; +using Nop.Services.Logging; +using Nop.Plugin.Misc.AuctionPlugin.Domains.Entities; +using Newtonsoft.Json.Serialization; +using Microsoft.AspNetCore.SignalR; + +namespace Nop.Plugin.Misc.AuctionPlugin.Hubs +{ + public class SignalRMessageHandler + { + protected readonly ILogger _logger; + protected readonly IProductService _productService; + protected readonly AuctionService _auctionService; + private IHubContext _hubContext; + + public SignalRMessageHandler(ILogger logger, IProductService productService, AuctionService auctionService, IHubContext hubContext) + { + _logger = logger; + _productService = productService; + _auctionService = auctionService; + _hubContext = hubContext; + } + + public async Task HandleMessage(MessageWrapper message) + { + switch (message.MessageType) + { + case "BidRequestMessage": + await HandleBidRequest(message.Data); + break; + + // Add other message types here + default: + await _logger.ErrorAsync("Unknown message type"); + break; + } + } + + private async Task HandleBidRequest(object data) + { + AuctionBidRequest bidRequestMessage = new AuctionBidRequest(); + try + { + var a = JsonConvert.DeserializeObject(data.ToString()); + //var b = (message.Data as JObject)?.ToObject(); + if (a == null) + { + await _logger.InformationAsync("Deserialization returned null. Message data might not match the expected structure."); + } + bidRequestMessage = a; + } + catch (Exception ex) + { + await _logger.ErrorAsync("Error during deserialization of AuctionBidRequest", ex); + } + //AuctionBidRequest bidRequestMessage = new AuctionBidRequest(); + + if (bidRequestMessage != null) + { + await _logger.InformationAsync($"Bid received: - Auction:{bidRequestMessage.AuctionId} Product: {bidRequestMessage.ProductId} - Bid: {bidRequestMessage.BidPrice} - Customer: {bidRequestMessage.CustomerId}"); + //get product + + var product = await _productService.GetProductByIdAsync(Convert.ToInt32(bidRequestMessage.ProductId)); + + if (product != null) + { + //validate the bidprice amount + + + //set new price + product.Price = Convert.ToDecimal(bidRequestMessage.BidPrice); + + } + + List mapping = await _auctionService.GetProductToAuctionByAuctionIdAndProductIdAsync(Convert.ToInt32(bidRequestMessage.AuctionId), Convert.ToInt32(bidRequestMessage.ProductId)); + + + AuctionBid auctionBid = new AuctionBid(); + + auctionBid.ProductId = Convert.ToInt32(bidRequestMessage.ProductId); + auctionBid.CustomerId = Convert.ToInt32(bidRequestMessage.CustomerId); + auctionBid.BidPrice = Convert.ToDecimal(bidRequestMessage.BidPrice); + auctionBid.ProductAuctionMappingId = mapping.FirstOrDefault().Id; + + //save bid + try + { + var result = _auctionService.InsertBidAsync(auctionBid); + + } + catch(Exception ex) + { + _logger.Error($"MessageHandling error: {ex.ToString()}"); + } + + //update product + + await _productService.UpdateProductAsync(product); + + // Optionally broadcast to all clients + var bid = new MessageWrapper + { + MessageType = "bidNotification", + Data = new BidNotificationMessage + { + ProductName = bidRequestMessage.ProductId, + BidPrice = bidRequestMessage.BidPrice, + NextStepAmount = "50000" + } + }; + var jsonMessage = JsonConvert.SerializeObject(bid, Formatting.Indented, + new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() } + ); + + await _hubContext.Clients.All.SendAsync("send", jsonMessage); + } + + } + } +} diff --git a/Nop.Plugin.Misc.AuctionPlugin/Infrastructure/PluginNopStartup.cs b/Nop.Plugin.Misc.AuctionPlugin/Infrastructure/PluginNopStartup.cs index ca6544b..415710c 100644 --- a/Nop.Plugin.Misc.AuctionPlugin/Infrastructure/PluginNopStartup.cs +++ b/Nop.Plugin.Misc.AuctionPlugin/Infrastructure/PluginNopStartup.cs @@ -65,6 +65,7 @@ namespace Nop.Plugin.Misc.AuctionPlugin.Infrastructure services.AddScoped(); services.AddScoped(); + services.AddScoped(); } /// diff --git a/Nop.Plugin.Misc.AuctionPlugin/Models/AuctionBidRequest.cs b/Nop.Plugin.Misc.AuctionPlugin/Models/AuctionBidRequest.cs index 295b41e..3c77d48 100644 --- a/Nop.Plugin.Misc.AuctionPlugin/Models/AuctionBidRequest.cs +++ b/Nop.Plugin.Misc.AuctionPlugin/Models/AuctionBidRequest.cs @@ -6,11 +6,12 @@ using System.Threading.Tasks; namespace Nop.Plugin.Misc.AuctionPlugin.Models { - internal class AuctionBidRequest + public class AuctionBidRequest { - public decimal BidPrice { get; set; } - public int ProductId { get; set; } - public int CustomerId { get; set; } + public string AuctionId { get; set; } + public string BidPrice { get; set; } + public string ProductId { get; set; } + public string CustomerId { get; set; } } } diff --git a/Nop.Plugin.Misc.AuctionPlugin/Models/ProductBidBoxViewModel.cs b/Nop.Plugin.Misc.AuctionPlugin/Models/ProductBidBoxViewModel.cs index a63414b..99b9c5f 100644 --- a/Nop.Plugin.Misc.AuctionPlugin/Models/ProductBidBoxViewModel.cs +++ b/Nop.Plugin.Misc.AuctionPlugin/Models/ProductBidBoxViewModel.cs @@ -9,6 +9,7 @@ namespace Nop.Plugin.Misc.AuctionPlugin.Models { public record ProductBidBoxViewModel: BaseNopModel { + public int AuctionId { get; set; } public int ProductId { get; set; } public int CustomerId { get; set; } diff --git a/Nop.Plugin.Misc.AuctionPlugin/Services/AuctionService.cs b/Nop.Plugin.Misc.AuctionPlugin/Services/AuctionService.cs index 924dcc5..544c1a0 100644 --- a/Nop.Plugin.Misc.AuctionPlugin/Services/AuctionService.cs +++ b/Nop.Plugin.Misc.AuctionPlugin/Services/AuctionService.cs @@ -50,11 +50,11 @@ public class AuctionService : IAuctionService /// Short term cache manager /// Cache manager public AuctionService( - AuctionDbContext ctx, + AuctionDbContext ctx, //IRepository auctionRepository, //IRepository productToAuctionRepository, - IShortTermCacheManager shortTermCacheManager, - IStaticCacheManager staticCacheManager, + IShortTermCacheManager shortTermCacheManager, + IStaticCacheManager staticCacheManager, IWorkContext workContext, ILogger logger) { @@ -183,31 +183,52 @@ public class AuctionService : IAuctionService return new AuctionBidDto(await _ctx.AuctionBids.GetByIdAsync(auctionBidId)); } + public async Task> GetProductToAuctionsByProductIdAsync(int productId) + { + return new List(await _ctx.ProductToAuctions.GetByProductId(productId).ToListAsync()); + } + + public async Task> GetProductToAuctionByAuctionIdAndProductIdAsync(int auctionId, int productId) + { + return new List(await _ctx.ProductToAuctions.GetByAuctionAndProductId(auctionId, productId).ToListAsync()); + } + public async Task AssignProductToAuctionAsync(int productId, decimal startingPrice, decimal bidPrice, int auctionId) { var auction = await _ctx.Auctions.GetByIdAsync(auctionId); if (auction == null) return false; + //check if it is added already - var mapping = new ProductToAuctionMapping + var productToAuction = _ctx.ProductToAuctions.GetByAuctionAndProductId(auctionId, productId); + if (productToAuction == null) { - ProductId = productId, - StartingPrice = startingPrice, - BidPrice = bidPrice, - ProductAmount = 0, - AuctionStatus = Domains.Enums.AuctionStatus.Active, - AuctionId = auctionId - }; - try - { - await _ctx.ProductToAuctions.InsertAsync(mapping); - } - catch (Exception ex) - { - await _logger.InformationAsync(ex.ToString()); - } - return true; + var mapping = new ProductToAuctionMapping + { + ProductId = productId, + StartingPrice = startingPrice, + BidPrice = bidPrice, + ProductAmount = 0, + AuctionStatus = Domains.Enums.AuctionStatus.Active, + AuctionId = auctionId + }; + try + { + await _ctx.ProductToAuctions.InsertAsync(mapping); + } + catch (Exception ex) + { + await _logger.InformationAsync(ex.ToString()); + } + + return true; + } + else + { + //already added + return false; + } } #endregion Dtos diff --git a/Nop.Plugin.Misc.AuctionPlugin/Services/IAuctionService.cs b/Nop.Plugin.Misc.AuctionPlugin/Services/IAuctionService.cs index 63a0b41..0411441 100644 --- a/Nop.Plugin.Misc.AuctionPlugin/Services/IAuctionService.cs +++ b/Nop.Plugin.Misc.AuctionPlugin/Services/IAuctionService.cs @@ -35,13 +35,20 @@ public interface IAuctionService Task> GetAllAuctionsAsync(); + Task> GetProductToAuctionsByAuctionIdAsync(int auctionId, bool onlyActiveItems = false); + Task GetAuctionDtoByIdAsync(int auctionId); + Task GetAuctionDtoWithProductByIdAsync(int auctionId, int productId); Task GetProductToAuctionDtoByIdAsync(int productToAuctionId); Task GetAuctionBidDtoByIdAsync(int auctionBidId); + Task> GetProductToAuctionsByProductIdAsync(int productId); + + Task> GetProductToAuctionByAuctionIdAndProductIdAsync(int auctionId, int productId); + Task AssignProductToAuctionAsync(int productId, decimal startingPrice, decimal bidPrice, int auctionId); } \ No newline at end of file diff --git a/Nop.Plugin.Misc.AuctionPlugin/Views/PublicProductBidBox.cshtml b/Nop.Plugin.Misc.AuctionPlugin/Views/PublicProductBidBox.cshtml index ff642cf..76f3128 100644 --- a/Nop.Plugin.Misc.AuctionPlugin/Views/PublicProductBidBox.cshtml +++ b/Nop.Plugin.Misc.AuctionPlugin/Views/PublicProductBidBox.cshtml @@ -30,7 +30,9 @@ - +