From 270602d8a308a2113288c4ee771e48d364b93dbb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 31 Jul 2025 02:56:54 +0000 Subject: [PATCH 1/3] Initial plan From 49a55ed85a4d537be32c8d797c257369dd6eb232 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 31 Jul 2025 03:05:06 +0000 Subject: [PATCH 2/3] Implement casino shop feature with database schema and core functionality Co-authored-by: Pierre-Demessence <1756398+Pierre-Demessence@users.noreply.github.com> --- DiscordBot/Domain/Casino/CasinoUser.cs | 33 ++++ DiscordBot/Extensions/CasinoRepository.cs | 32 +++ .../Modules/Casino/CasinoSlashModule.cs | 185 ++++++++++++++++++ DiscordBot/Services/Casino/CasinoService.cs | 178 +++++++++++++++++ DiscordBot/Services/DatabaseService.cs | 24 +++ 5 files changed, 452 insertions(+) diff --git a/DiscordBot/Domain/Casino/CasinoUser.cs b/DiscordBot/Domain/Casino/CasinoUser.cs index b6cf09db..dcd86ae2 100644 --- a/DiscordBot/Domain/Casino/CasinoUser.cs +++ b/DiscordBot/Domain/Casino/CasinoUser.cs @@ -45,6 +45,7 @@ public enum TransactionType Gift, Game, Admin, + Shop, } public class GameStatistics @@ -72,10 +73,29 @@ public class GameLeaderboardEntry public string? GameName { get; set; } // null for global leaderboard } +public class ShopItem +{ + public int Id { get; set; } + public required string Title { get; set; } + public required string Description { get; set; } + public ulong Price { get; set; } + public DateTime CreatedAt { get; set; } +} + +public class ShopPurchase +{ + public int Id { get; set; } + public required string UserID { get; set; } + public int ItemId { get; set; } + public DateTime PurchaseDate { get; set; } +} + public static class CasinoProps { public const string CasinoTableName = "casino_users"; public const string TransactionTableName = "token_transactions"; + public const string ShopItemsTableName = "shop_items"; + public const string ShopPurchasesTableName = "shop_purchases"; // CasinoUser properties public const string Id = nameof(CasinoUser.Id); @@ -92,4 +112,17 @@ public static class CasinoProps public const string TransactionType = nameof(TokenTransaction.Type); public const string Details = nameof(TokenTransaction.DetailsJson); public const string TransactionCreatedAt = nameof(TokenTransaction.CreatedAt); + + // ShopItem properties + public const string ShopItemId = nameof(ShopItem.Id); + public const string ShopItemTitle = nameof(ShopItem.Title); + public const string ShopItemDescription = nameof(ShopItem.Description); + public const string ShopItemPrice = nameof(ShopItem.Price); + public const string ShopItemCreatedAt = nameof(ShopItem.CreatedAt); + + // ShopPurchase properties + public const string ShopPurchaseId = nameof(ShopPurchase.Id); + public const string ShopPurchaseUserID = nameof(ShopPurchase.UserID); + public const string ShopPurchaseItemId = nameof(ShopPurchase.ItemId); + public const string ShopPurchaseDate = nameof(ShopPurchase.PurchaseDate); } \ No newline at end of file diff --git a/DiscordBot/Extensions/CasinoRepository.cs b/DiscordBot/Extensions/CasinoRepository.cs index e63c145c..522ebaae 100644 --- a/DiscordBot/Extensions/CasinoRepository.cs +++ b/DiscordBot/Extensions/CasinoRepository.cs @@ -52,4 +52,36 @@ public interface ICasinoRepo // Test connection [Sql($"SELECT COUNT(*) FROM {CasinoProps.CasinoTableName}")] Task TestCasinoConnection(); + + // Shop Item Operations + [Sql($@" + INSERT INTO {CasinoProps.ShopItemsTableName} ({CasinoProps.ShopItemTitle}, {CasinoProps.ShopItemDescription}, {CasinoProps.ShopItemPrice}, {CasinoProps.ShopItemCreatedAt}) + VALUES (@{CasinoProps.ShopItemTitle}, @{CasinoProps.ShopItemDescription}, @{CasinoProps.ShopItemPrice}, @{CasinoProps.ShopItemCreatedAt}); + SELECT * FROM {CasinoProps.ShopItemsTableName} WHERE {CasinoProps.ShopItemId} = LAST_INSERT_ID()")] + Task InsertShopItem(ShopItem item); + + [Sql($"SELECT * FROM {CasinoProps.ShopItemsTableName} ORDER BY {CasinoProps.ShopItemPrice} ASC")] + Task> GetAllShopItems(); + + [Sql($"SELECT * FROM {CasinoProps.ShopItemsTableName} WHERE {CasinoProps.ShopItemId} = @itemId")] + Task GetShopItem(int itemId); + + [Sql($"DELETE FROM {CasinoProps.ShopItemsTableName}")] + Task ClearAllShopItems(); + + // Shop Purchase Operations + [Sql($@" + INSERT INTO {CasinoProps.ShopPurchasesTableName} ({CasinoProps.ShopPurchaseUserID}, {CasinoProps.ShopPurchaseItemId}, {CasinoProps.ShopPurchaseDate}) + VALUES (@{CasinoProps.ShopPurchaseUserID}, @{CasinoProps.ShopPurchaseItemId}, @{CasinoProps.ShopPurchaseDate}); + SELECT * FROM {CasinoProps.ShopPurchasesTableName} WHERE {CasinoProps.ShopPurchaseId} = LAST_INSERT_ID()")] + Task InsertShopPurchase(ShopPurchase purchase); + + [Sql($"SELECT * FROM {CasinoProps.ShopPurchasesTableName} WHERE {CasinoProps.ShopPurchaseUserID} = @userId")] + Task> GetUserShopPurchases(string userId); + + [Sql($"SELECT COUNT(*) FROM {CasinoProps.ShopPurchasesTableName} WHERE {CasinoProps.ShopPurchaseUserID} = @userId AND {CasinoProps.ShopPurchaseItemId} = @itemId")] + Task CheckUserHasItem(string userId, int itemId); + + [Sql($"DELETE FROM {CasinoProps.ShopPurchasesTableName}")] + Task ClearAllShopPurchases(); } \ No newline at end of file diff --git a/DiscordBot/Modules/Casino/CasinoSlashModule.cs b/DiscordBot/Modules/Casino/CasinoSlashModule.cs index fec62157..91e2d01c 100644 --- a/DiscordBot/Modules/Casino/CasinoSlashModule.cs +++ b/DiscordBot/Modules/Casino/CasinoSlashModule.cs @@ -355,6 +355,7 @@ public async Task NavigateHistory(string userId, string pageStr, string requestT TransactionType.Gift => GetGiftDisplay(transaction), TransactionType.Game => GetGameDisplay(transaction), TransactionType.Admin => GetAdminDisplay(transaction), + TransactionType.Shop => GetShopDisplay(transaction), _ => ("❓", transaction.Type.ToString(), "") }; @@ -416,6 +417,16 @@ public async Task NavigateHistory(string userId, string pageStr, string requestT return ("⚙️", title, description); } + private (string emoji, string title, string description) GetShopDisplay(TokenTransaction transaction) + { + var itemTitle = transaction.Details?.GetValueOrDefault("item_title"); + + string title = "Shop Purchase"; + if (itemTitle != null) title = $"Bought {itemTitle}"; + + return ("🛒", title, ""); + } + private string CapitalizeFirst(string input) { if (string.IsNullOrEmpty(input)) @@ -541,6 +552,180 @@ public async Task Daily() #endregion + #region Shop Command + + [SlashCommand("shop", "View and purchase items from the casino shop")] + public async Task Shop() + { + if (!await CheckChannelPermissions()) return; + + try + { + await Context.Interaction.DeferAsync(ephemeral: true); + + // Ensure shop items are initialized + await CasinoService.InitializeDefaultShopItems(); + + var user = await CasinoService.GetOrCreateCasinoUser(Context.User.Id.ToString()); + var allItems = await CasinoService.GetAllShopItems(); + var userPurchases = await CasinoService.GetUserShopPurchases(Context.User.Id.ToString()); + var purchasedItemIds = userPurchases.Select(p => p.ItemId).ToHashSet(); + + if (allItems.Count == 0) + { + await Context.Interaction.FollowupAsync("🛒 The shop is currently empty. Please check back later!", ephemeral: true); + return; + } + + var embed = new EmbedBuilder() + .WithTitle("🛒 Casino Shop") + .WithDescription($"**Your Balance:** {user.Tokens:N0} tokens\n\nSelect an item from the dropdown below to purchase it.") + .WithColor(Color.Purple); + + // Add shop items to embed (strike through purchased items) + foreach (var item in allItems) + { + var isPurchased = purchasedItemIds.Contains(item.Id); + var titleDisplay = isPurchased ? $"~~{item.Title}~~" : item.Title; + var statusText = isPurchased ? " ✅ **OWNED**" : $" - **{item.Price:N0} tokens**"; + + embed.AddField($"{titleDisplay}{statusText}", item.Description, false); + } + + // Create dropdown with available items (not purchased) + var availableItems = allItems.Where(item => !purchasedItemIds.Contains(item.Id)).ToList(); + + if (availableItems.Count == 0) + { + embed.WithFooter("🎉 You own all available shop items!"); + await Context.Interaction.FollowupAsync(embed: embed.Build(), ephemeral: true); + return; + } + + var selectMenu = new SelectMenuBuilder() + .WithPlaceholder("Select an item to purchase...") + .WithCustomId($"shop_purchase:{Context.User.Id}") + .WithMinValues(1) + .WithMaxValues(1); + + foreach (var item in availableItems.Take(25)) // Discord limit of 25 options + { + var canAfford = user.Tokens >= item.Price; + var label = $"{item.Title} - {item.Price:N0} tokens"; + if (!canAfford) label += " (Can't afford)"; + + selectMenu.AddOption( + label: label, + value: item.Id.ToString(), + description: item.Description.Length > 100 ? item.Description.Substring(0, 97) + "..." : item.Description, + isDefault: false + ); + } + + var components = new ComponentBuilder() + .WithSelectMenu(selectMenu) + .Build(); + + await Context.Interaction.FollowupAsync(embed: embed.Build(), components: components, ephemeral: true); + } + catch (Exception ex) + { + await LoggingService.LogChannelAndFile($"Casino: ERROR in Shop command for user {Context.User.Username} (ID: {Context.User.Id}): {ex.Message}", ExtendedLogSeverity.Error); + await LoggingService.LogChannelAndFile($"Casino: Shop Exception Details: {ex}"); + + try + { + if (!Context.Interaction.HasResponded) + { + await Context.Interaction.RespondAsync("❌ An error occurred while loading the shop. Please try again.", ephemeral: true); + } + else + { + await Context.Interaction.FollowupAsync("❌ An error occurred while loading the shop. Please try again.", ephemeral: true); + } + } + catch + { + await LoggingService.LogChannelAndFile($"Casino: Failed to send error response to user {Context.User.Username} in Shop command"); + } + } + } + + [ComponentInteraction("shop_purchase:*", true)] + public async Task HandleShopPurchase(string userId, string[] selectedValues) + { + try + { + await Context.Interaction.DeferAsync(ephemeral: true); + + if (Context.User.Id.ToString() != userId) + { + await Context.Interaction.FollowupAsync("🚫 You are not authorized to make this purchase.", ephemeral: true); + return; + } + + if (selectedValues == null || selectedValues.Length == 0) + { + await Context.Interaction.FollowupAsync("❌ No item selected.", ephemeral: true); + return; + } + + if (!int.TryParse(selectedValues[0], out int itemId)) + { + await Context.Interaction.FollowupAsync("❌ Invalid item selected.", ephemeral: true); + return; + } + + var (success, message) = await CasinoService.PurchaseShopItem(Context.User.Id.ToString(), itemId); + + if (success) + { + var embed = new EmbedBuilder() + .WithTitle("🎉 Purchase Successful!") + .WithDescription(message) + .WithColor(Color.Green) + .WithCurrentTimestamp() + .Build(); + + await Context.Interaction.FollowupAsync(embed: embed, ephemeral: true); + await LoggingService.LogChannelAndFile($"Casino: {Context.User.Username} successfully purchased shop item {itemId}"); + } + else + { + var embed = new EmbedBuilder() + .WithTitle("❌ Purchase Failed") + .WithDescription(message) + .WithColor(Color.Red) + .Build(); + + await Context.Interaction.FollowupAsync(embed: embed, ephemeral: true); + } + } + catch (Exception ex) + { + await LoggingService.LogChannelAndFile($"Casino: ERROR in HandleShopPurchase for user {Context.User.Username} (ID: {Context.User.Id}): {ex.Message}", ExtendedLogSeverity.Error); + await LoggingService.LogChannelAndFile($"Casino: HandleShopPurchase Exception Details: {ex}"); + + try + { + if (!Context.Interaction.HasResponded) + { + await Context.Interaction.RespondAsync("❌ An error occurred while processing your purchase. Please try again.", ephemeral: true); + } + else + { + await Context.Interaction.FollowupAsync("❌ An error occurred while processing your purchase. Please try again.", ephemeral: true); + } + } + catch + { + await LoggingService.LogChannelAndFile($"Casino: Failed to send error response to user {Context.User.Username} in HandleShopPurchase"); + } + } + } + + #endregion + #region Admin Reset Command [SlashCommand("reset", "Reset all casino data (Admin only) - REQUIRES CONFIRMATION")] diff --git a/DiscordBot/Services/Casino/CasinoService.cs b/DiscordBot/Services/Casino/CasinoService.cs index 1e5a1d8e..6cb49291 100644 --- a/DiscordBot/Services/Casino/CasinoService.cs +++ b/DiscordBot/Services/Casino/CasinoService.cs @@ -329,12 +329,190 @@ public async Task GetNextDailyRewardTime(string userId) #endregion + #region Shop Functions + + public async Task> GetAllShopItems() + { + try + { + var items = await _databaseService.CasinoQuery.GetAllShopItems(); + return items.ToList(); + } + catch (Exception ex) + { + await _loggingService.LogChannelAndFile($"{ServiceName}: ERROR in GetAllShopItems: {ex.Message}", ExtendedLogSeverity.Error); + throw new InvalidOperationException($"Failed to get shop items: {ex.Message}", ex); + } + } + + public async Task GetShopItem(int itemId) + { + try + { + return await _databaseService.CasinoQuery.GetShopItem(itemId); + } + catch (Exception ex) + { + await _loggingService.LogChannelAndFile($"{ServiceName}: ERROR in GetShopItem for itemId {itemId}: {ex.Message}", ExtendedLogSeverity.Error); + throw new InvalidOperationException($"Failed to get shop item: {ex.Message}", ex); + } + } + + public async Task> GetUserShopPurchases(string userId) + { + try + { + var purchases = await _databaseService.CasinoQuery.GetUserShopPurchases(userId); + return purchases.ToList(); + } + catch (Exception ex) + { + await _loggingService.LogChannelAndFile($"{ServiceName}: ERROR in GetUserShopPurchases for userId {userId}: {ex.Message}", ExtendedLogSeverity.Error); + throw new InvalidOperationException($"Failed to get user shop purchases: {ex.Message}", ex); + } + } + + public async Task UserHasItem(string userId, int itemId) + { + try + { + var count = await _databaseService.CasinoQuery.CheckUserHasItem(userId, itemId); + return count > 0; + } + catch (Exception ex) + { + await _loggingService.LogChannelAndFile($"{ServiceName}: ERROR in UserHasItem for userId {userId}, itemId {itemId}: {ex.Message}", ExtendedLogSeverity.Error); + throw new InvalidOperationException($"Failed to check if user has item: {ex.Message}", ex); + } + } + + public async Task<(bool success, string message)> PurchaseShopItem(string userId, int itemId) + { + try + { + // Check if user has enough tokens + var user = await GetOrCreateCasinoUser(userId); + var item = await GetShopItem(itemId); + + if (item == null) + { + return (false, "Shop item not found."); + } + + // Check if user already has this item + if (await UserHasItem(userId, itemId)) + { + return (false, "You already own this item."); + } + + if (user.Tokens < item.Price) + { + return (false, $"Insufficient tokens. You need {item.Price:N0} tokens but only have {user.Tokens:N0}."); + } + + // Deduct tokens and record transaction + var newBalance = user.Tokens - item.Price; + await _databaseService.CasinoQuery.UpdateTokens(userId, newBalance, DateTime.UtcNow); + + // Record the purchase transaction + await RecordTransaction(userId, -(long)item.Price, TransactionType.Shop, new Dictionary + { + ["item_id"] = itemId.ToString(), + ["item_title"] = item.Title + }); + + // Record the purchase + var purchase = new ShopPurchase + { + UserID = userId, + ItemId = itemId, + PurchaseDate = DateTime.UtcNow + }; + await _databaseService.CasinoQuery.InsertShopPurchase(purchase); + + await _loggingService.LogChannelAndFile($"{ServiceName}: User {userId} purchased shop item '{item.Title}' for {item.Price} tokens"); + return (true, $"Successfully purchased '{item.Title}' for {item.Price:N0} tokens!"); + } + catch (Exception ex) + { + await _loggingService.LogChannelAndFile($"{ServiceName}: ERROR in PurchaseShopItem for userId {userId}, itemId {itemId}: {ex.Message}", ExtendedLogSeverity.Error); + throw new InvalidOperationException($"Failed to purchase shop item: {ex.Message}", ex); + } + } + + public async Task InitializeDefaultShopItems() + { + try + { + var existingItems = await GetAllShopItems(); + if (existingItems.Count > 0) + { + await _loggingService.LogChannelAndFile($"{ServiceName}: Shop items already exist, skipping initialization"); + return; + } + + var defaultItems = new List + { + new ShopItem + { + Title = "Auto Daily Tokens", + Description = "Automatically retrieve your daily tokens every day", + Price = 20000, + CreatedAt = DateTime.UtcNow + }, + new ShopItem + { + Title = "Badge: Casino Lover", + Description = "Show your love for the casino with this special badge", + Price = 1000, + CreatedAt = DateTime.UtcNow + }, + new ShopItem + { + Title = "Badge: High Roller", + Description = "A badge for those who aren't afraid to bet big", + Price = 10000, + CreatedAt = DateTime.UtcNow + }, + new ShopItem + { + Title = "Badge: Token Master", + Description = "Master of token accumulation and casino strategy", + Price = 100000, + CreatedAt = DateTime.UtcNow + }, + new ShopItem + { + Title = "Badge: Millionaire", + Description = "The ultimate badge for reaching millionaire status", + Price = 1000000, + CreatedAt = DateTime.UtcNow + } + }; + + foreach (var item in defaultItems) + { + await _databaseService.CasinoQuery.InsertShopItem(item); + } + + await _loggingService.LogChannelAndFile($"{ServiceName}: Initialized {defaultItems.Count} default shop items"); + } + catch (Exception ex) + { + await _loggingService.LogChannelAndFile($"{ServiceName}: ERROR in InitializeDefaultShopItems: {ex.Message}", ExtendedLogSeverity.Error); + throw new InvalidOperationException($"Failed to initialize default shop items: {ex.Message}", ex); + } + } + + #endregion + #region Admin Functions public async Task ResetAllCasinoData() { await _databaseService.CasinoQuery.ClearAllCasinoUsers(); await _databaseService.CasinoQuery.ClearAllTransactions(); + await _databaseService.CasinoQuery.ClearAllShopPurchases(); await _loggingService.LogChannelAndFile($"{ServiceName}: All casino data has been reset."); } diff --git a/DiscordBot/Services/DatabaseService.cs b/DiscordBot/Services/DatabaseService.cs index 82df7e62..3244032e 100644 --- a/DiscordBot/Services/DatabaseService.cs +++ b/DiscordBot/Services/DatabaseService.cs @@ -158,6 +158,30 @@ await _logging.LogAction( $"PRIMARY KEY (`{CasinoProps.TransactionId}`), " + $"KEY `idx_user_created` (`{CasinoProps.TransactionUserID}`, `{CasinoProps.TransactionCreatedAt}`) " + $") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"); + + // Create shop_items table + c.ExecuteSql( + $"CREATE TABLE `{CasinoProps.ShopItemsTableName}` (" + + $"`{CasinoProps.ShopItemId}` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, " + + $"`{CasinoProps.ShopItemTitle}` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL, " + + $"`{CasinoProps.ShopItemDescription}` text COLLATE utf8mb4_unicode_ci NOT NULL, " + + $"`{CasinoProps.ShopItemPrice}` bigint(20) UNSIGNED NOT NULL, " + + $"`{CasinoProps.ShopItemCreatedAt}` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, " + + $"PRIMARY KEY (`{CasinoProps.ShopItemId}`) " + + $") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"); + + // Create shop_purchases table + c.ExecuteSql( + $"CREATE TABLE `{CasinoProps.ShopPurchasesTableName}` (" + + $"`{CasinoProps.ShopPurchaseId}` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, " + + $"`{CasinoProps.ShopPurchaseUserID}` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL, " + + $"`{CasinoProps.ShopPurchaseItemId}` int(11) UNSIGNED NOT NULL, " + + $"`{CasinoProps.ShopPurchaseDate}` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, " + + $"PRIMARY KEY (`{CasinoProps.ShopPurchaseId}`), " + + $"KEY `idx_user_item` (`{CasinoProps.ShopPurchaseUserID}`, `{CasinoProps.ShopPurchaseItemId}`), " + + $"KEY `fk_item_id` (`{CasinoProps.ShopPurchaseItemId}`), " + + $"CONSTRAINT `fk_shop_purchases_item_id` FOREIGN KEY (`{CasinoProps.ShopPurchaseItemId}`) REFERENCES `{CasinoProps.ShopItemsTableName}` (`{CasinoProps.ShopItemId}`) ON DELETE CASCADE " + + $") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"); } catch (Exception e) { From 2f57877db28731af8a2ebd825c1013f570ae9462 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 31 Jul 2025 03:06:58 +0000 Subject: [PATCH 3/3] Add shop table creation for existing casino installations Co-authored-by: Pierre-Demessence <1756398+Pierre-Demessence@users.noreply.github.com> --- DiscordBot/Services/DatabaseService.cs | 47 ++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/DiscordBot/Services/DatabaseService.cs b/DiscordBot/Services/DatabaseService.cs index 3244032e..a9542055 100644 --- a/DiscordBot/Services/DatabaseService.cs +++ b/DiscordBot/Services/DatabaseService.cs @@ -126,6 +126,53 @@ await _logging.LogAction($"DatabaseService: Table '{UserProps.TableName}' genera await _logging.LogAction( $"DatabaseService: Connected to casino tables successfully. {casinoUserCount} casino users in database.", ExtendedLogSeverity.Positive); + + // Check if shop tables exist, create them if they don't + try + { + await CasinoQuery.GetAllShopItems(); + await _logging.LogAction("DatabaseService: Shop tables already exist.", ExtendedLogSeverity.Positive); + } + catch + { + await _logging.LogAction("DatabaseService: Shop tables do not exist, creating them.", + ExtendedLogSeverity.LowWarning); + try + { + // Create shop_items table + c.ExecuteSql( + $"CREATE TABLE `{CasinoProps.ShopItemsTableName}` (" + + $"`{CasinoProps.ShopItemId}` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, " + + $"`{CasinoProps.ShopItemTitle}` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL, " + + $"`{CasinoProps.ShopItemDescription}` text COLLATE utf8mb4_unicode_ci NOT NULL, " + + $"`{CasinoProps.ShopItemPrice}` bigint(20) UNSIGNED NOT NULL, " + + $"`{CasinoProps.ShopItemCreatedAt}` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, " + + $"PRIMARY KEY (`{CasinoProps.ShopItemId}`) " + + $") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"); + + // Create shop_purchases table + c.ExecuteSql( + $"CREATE TABLE `{CasinoProps.ShopPurchasesTableName}` (" + + $"`{CasinoProps.ShopPurchaseId}` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, " + + $"`{CasinoProps.ShopPurchaseUserID}` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL, " + + $"`{CasinoProps.ShopPurchaseItemId}` int(11) UNSIGNED NOT NULL, " + + $"`{CasinoProps.ShopPurchaseDate}` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, " + + $"PRIMARY KEY (`{CasinoProps.ShopPurchaseId}`), " + + $"KEY `idx_user_item` (`{CasinoProps.ShopPurchaseUserID}`, `{CasinoProps.ShopPurchaseItemId}`), " + + $"KEY `fk_item_id` (`{CasinoProps.ShopPurchaseItemId}`), " + + $"CONSTRAINT `fk_shop_purchases_item_id` FOREIGN KEY (`{CasinoProps.ShopPurchaseItemId}`) REFERENCES `{CasinoProps.ShopItemsTableName}` (`{CasinoProps.ShopItemId}`) ON DELETE CASCADE " + + $") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"); + + await _logging.LogAction("DatabaseService: Shop tables created successfully.", + ExtendedLogSeverity.Positive); + } + catch (Exception e) + { + await _logging.LogAction( + $"SQL Exception: Failed to create shop tables.\nMessage: {e}", + ExtendedLogSeverity.Critical); + } + } } catch {