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 78eec8d..8ef09e5 100644 Binary files a/LittleShop/littleshop.db-shm and b/LittleShop/littleshop.db-shm differ diff --git a/LittleShop/littleshop.db-wal b/LittleShop/littleshop.db-wal index de2c1ac..fb66879 100644 Binary files a/LittleShop/littleshop.db-wal and b/LittleShop/littleshop.db-wal differ 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();