From 7e364b2a44edc44a33442897a092aaee94658440 Mon Sep 17 00:00:00 2001 From: sysadmin Date: Thu, 28 Aug 2025 00:22:27 +0100 Subject: [PATCH] "Improve-product-UI-with-individual-bubbles-and-fix-admin-authentication" --- .../Admin/Controllers/AccountController.cs | 3 +- .../Admin/Views/Account/AccessDenied.cshtml | 36 ++++++++++++ LittleShop/Controllers/TestController.cs | 54 +++++++++++++++++- LittleShop/littleshop.db-shm | Bin 32768 -> 32768 bytes LittleShop/littleshop.db-wal | Bin 828152 -> 1050632 bytes TeleBot/TeleBot/Handlers/CallbackHandler.cs | 31 +++++++++- TeleBot/TeleBot/UI/MenuBuilder.cs | 32 +++++++++++ TeleBot/TeleBot/UI/MessageFormatter.cs | 19 ++++++ 8 files changed, 171 insertions(+), 4 deletions(-) create mode 100644 LittleShop/Areas/Admin/Views/Account/AccessDenied.cshtml diff --git a/LittleShop/Areas/Admin/Controllers/AccountController.cs b/LittleShop/Areas/Admin/Controllers/AccountController.cs index b724d21..1636c1d 100644 --- a/LittleShop/Areas/Admin/Controllers/AccountController.cs +++ b/LittleShop/Areas/Admin/Controllers/AccountController.cs @@ -43,7 +43,8 @@ public class AccountController : Controller var claims = new List { new(ClaimTypes.Name, "admin"), - new(ClaimTypes.NameIdentifier, Guid.NewGuid().ToString()) + new(ClaimTypes.NameIdentifier, Guid.NewGuid().ToString()), + new(ClaimTypes.Role, "Admin") }; var identity = new ClaimsIdentity(claims, "Cookies"); diff --git a/LittleShop/Areas/Admin/Views/Account/AccessDenied.cshtml b/LittleShop/Areas/Admin/Views/Account/AccessDenied.cshtml new file mode 100644 index 0000000..4d75ade --- /dev/null +++ b/LittleShop/Areas/Admin/Views/Account/AccessDenied.cshtml @@ -0,0 +1,36 @@ +@{ + ViewData["Title"] = "Access Denied"; +} + +
+
+
+
+
+
+ +

Access Denied

+
+ + + +

+ You need to log in with an administrator account to access the admin panel. +

+ + +
+
+
+
+
\ No newline at end of file diff --git a/LittleShop/Controllers/TestController.cs b/LittleShop/Controllers/TestController.cs index 7893202..4fa8f0e 100644 --- a/LittleShop/Controllers/TestController.cs +++ b/LittleShop/Controllers/TestController.cs @@ -1,6 +1,8 @@ using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; using LittleShop.Services; using LittleShop.DTOs; +using LittleShop.Data; namespace LittleShop.Controllers; @@ -10,11 +12,13 @@ public class TestController : ControllerBase { private readonly ICategoryService _categoryService; private readonly IProductService _productService; + private readonly LittleShopContext _context; - public TestController(ICategoryService categoryService, IProductService productService) + public TestController(ICategoryService categoryService, IProductService productService, LittleShopContext context) { _categoryService = categoryService; _productService = productService; + _context = context; } [HttpPost("create-product")] @@ -86,4 +90,52 @@ public class TestController : ControllerBase return BadRequest(new { error = ex.Message }); } } + + [HttpPost("cleanup-bots")] + public async Task CleanupBots() + { + try + { + // Get count before cleanup + var totalBots = await _context.Bots.CountAsync(); + + // Keep only the most recent active bot per platform + var keepBots = await _context.Bots + .Where(b => b.IsActive && b.Status == Enums.BotStatus.Active) + .GroupBy(b => b.PlatformId) + .Select(g => g.OrderByDescending(b => b.LastSeenAt ?? b.CreatedAt).First()) + .ToListAsync(); + + var keepBotIds = keepBots.Select(b => b.Id).ToList(); + + // Delete old/inactive bots and related data + var botsToDelete = await _context.Bots + .Where(b => !keepBotIds.Contains(b.Id)) + .ToListAsync(); + + _context.Bots.RemoveRange(botsToDelete); + await _context.SaveChangesAsync(); + + var deletedCount = botsToDelete.Count; + var remainingCount = keepBots.Count; + + return Ok(new { + message = "Bot cleanup completed", + totalBots = totalBots, + deletedBots = deletedCount, + remainingBots = remainingCount, + keptBots = keepBots.Select(b => new { + id = b.Id, + name = b.Name, + platformUsername = b.PlatformUsername, + lastSeen = b.LastSeenAt, + created = b.CreatedAt + }) + }); + } + catch (Exception ex) + { + return BadRequest(new { error = ex.Message }); + } + } } \ No newline at end of file diff --git a/LittleShop/littleshop.db-shm b/LittleShop/littleshop.db-shm index 78eec8df44afcf8419121e0e5e8d820d7828024e..8ef09e576e16189b346025cf3f3fcee7df502835 100644 GIT binary patch delta 625 zcmb7BTS!!45dP-;rsr%4iJL_9kO*Ryq4W^cCe#{UBFyk1D5OX+@Xbq?mFNi3b2<=# zA^DO9<$5|j8UztRUeeUcTeeoZUF>3RmTBAUIU&6|F#PjfX6EPrO;a;X?c&C%EW{!~ zl7RJ5IsKC1<6H7BWnG@gR8_%ndHC?~LSb|0%=MG2^~)$HVsoSa#XnlZxYe*t@dHg? z$u8QyW|QbSJffvhd}C`eo8%LDvwcsc`;0VXqXZ`~Xj|%_rmkXw$seR4+w%7kycV<$>+ z3eR1o=a5JBV@$wf;v2;}y>NIV6+2LjG7Px3`I3ypC+I;R-XVeo{NmWAmLneZ660dY z*LbeJbRM^TAUS6z!-)ZrH1JF0!{*zN@N8q0$26E~G-BzB`0 zPw@di@Dqz{sR(yfcvT309E*LWGMCg8qV~Y)hjH{#$cSbnV;|1pHa@w~i~Tr{JD9Z_ zYqk>22`d-bApajg2R;jwSLr;f(|1=%K_2Q6#1~iT2T_}T57{~wT?9G{RK}KAOu@;e!3&pH_wY6F^rj+tIt;mgU6MMH@Ywgr>f#d?K z*~aVAg)1TC(zPqs5+&j6eW%vYBB@bF^?l5 zyCbXyEf|Df^{_nHuz}DnC#wt=8O$M?H>CuK+9H>T~Sj*B} KSTTn~Rr>?{lb@IX diff --git a/LittleShop/littleshop.db-wal b/LittleShop/littleshop.db-wal index de2c1acb6e90a7367e48666521d7679e89ddf57c..fb66879d06e8a8763ed6d58e02f303f1005e2369 100644 GIT binary patch delta 5108 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 J|AECt{THGEo-Y6Z delta 33 ocmeB};PB(9Q9}!33sVbo3rh=Y3tJ0&3r7oQ3s(#G7M>rA0M!Z%5C8xG diff --git a/TeleBot/TeleBot/Handlers/CallbackHandler.cs b/TeleBot/TeleBot/Handlers/CallbackHandler.cs index 43ba7e1..615895d 100644 --- a/TeleBot/TeleBot/Handlers/CallbackHandler.cs +++ b/TeleBot/TeleBot/Handlers/CallbackHandler.cs @@ -219,13 +219,40 @@ namespace TeleBot.Handlers categoryName = categories.FirstOrDefault(c => c.Id == categoryId)?.Name; } + // Edit the original message to show category header + var headerText = !string.IsNullOrEmpty(categoryName) + ? $"📦 *Products in {categoryName}*\n\nBrowse products below:" + : "📦 *All Products*\n\nBrowse products below:"; + await bot.EditMessageTextAsync( message.Chat.Id, message.MessageId, - MessageFormatter.FormatProductList(products, categoryName), + headerText, parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, - replyMarkup: MenuBuilder.ProductListMenu(products, categoryId, page) + replyMarkup: MenuBuilder.CategoryNavigationMenu(categoryId) ); + + // Send individual product bubbles + if (products.Items.Any()) + { + foreach (var product in products.Items) + { + await bot.SendTextMessageAsync( + message.Chat.Id, + MessageFormatter.FormatSingleProduct(product), + parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, + replyMarkup: MenuBuilder.SingleProductMenu(product.Id) + ); + } + } + else + { + await bot.SendTextMessageAsync( + message.Chat.Id, + "No products available in this category.", + replyMarkup: MenuBuilder.BackToCategoriesMenu() + ); + } } private async Task HandleProductDetail(ITelegramBotClient bot, Message message, UserSession session, Guid productId) diff --git a/TeleBot/TeleBot/UI/MenuBuilder.cs b/TeleBot/TeleBot/UI/MenuBuilder.cs index dc39961..9594d08 100644 --- a/TeleBot/TeleBot/UI/MenuBuilder.cs +++ b/TeleBot/TeleBot/UI/MenuBuilder.cs @@ -320,5 +320,37 @@ namespace TeleBot.UI _ => "📋" }; } + + public static InlineKeyboardMarkup SingleProductMenu(Guid productId) + { + return new InlineKeyboardMarkup(new[] + { + new[] { + InlineKeyboardButton.WithCallbackData("🛒 Quick Buy", $"add:{productId}:1"), + InlineKeyboardButton.WithCallbackData("📄 Details", $"product:{productId}") + } + }); + } + + public static InlineKeyboardMarkup CategoryNavigationMenu(Guid? categoryId) + { + return new InlineKeyboardMarkup(new[] + { + new[] { + InlineKeyboardButton.WithCallbackData("⬅️ Back to Categories", "browse"), + InlineKeyboardButton.WithCallbackData("🏠 Main Menu", "menu") + } + }); + } + + public static InlineKeyboardMarkup BackToCategoriesMenu() + { + return new InlineKeyboardMarkup(new[] + { + new[] { + InlineKeyboardButton.WithCallbackData("⬅️ Back to Categories", "browse") + } + }); + } } } \ No newline at end of file diff --git a/TeleBot/TeleBot/UI/MessageFormatter.cs b/TeleBot/TeleBot/UI/MessageFormatter.cs index 65e9fb2..125a95f 100644 --- a/TeleBot/TeleBot/UI/MessageFormatter.cs +++ b/TeleBot/TeleBot/UI/MessageFormatter.cs @@ -89,6 +89,25 @@ namespace TeleBot.UI return sb.ToString(); } + public static string FormatSingleProduct(Product product) + { + var sb = new StringBuilder(); + + sb.AppendLine($"🛍️ *{product.Name}*"); + sb.AppendLine($"💰 £{product.Price:F2}"); + + if (!string.IsNullOrEmpty(product.Description)) + { + // Truncate description for bubble format + var desc = product.Description.Length > 100 + ? product.Description.Substring(0, 100) + "..." + : product.Description; + sb.AppendLine($"\n_{desc}_"); + } + + return sb.ToString(); + } + public static string FormatProductDetail(Product product) { var sb = new StringBuilder();