From 3b10e77c827e3c4b945d7d97f1ba680412c7848e Mon Sep 17 00:00:00 2001 From: VinceC <33974776+VinceC3@users.noreply.github.com> Date: Sun, 24 Nov 2024 03:49:23 -0600 Subject: [PATCH] supabase setup --- src/Bot.js | 20 ++- src/commands/SubscriptionCommands.js | 234 +++++++++++++++++++++------ src/services/SupabaseService.js | 179 ++++++++++++++------ 3 files changed, 330 insertions(+), 103 deletions(-) diff --git a/src/Bot.js b/src/Bot.js index 4b01d39..98e7d84 100644 --- a/src/Bot.js +++ b/src/Bot.js @@ -38,9 +38,19 @@ class Bot { this.client.on('interactionCreate', this.handleInteraction.bind(this)); } - async start(token) { - console.log("Starting bot..."); - try { + // In the start method, add Supabase connection test: +async start(token) { + console.log("Starting bot..."); + try { + // Test Supabase connection first + if (this.supabaseService) { + const connected = await this.supabaseService.testConnection(); + if (!connected) { + console.warn('Supabase connection failed. Subscription features will be disabled.'); + this.subscriptionCommands = null; + } + } + await this.client.login(token); console.log("Login successful"); @@ -48,11 +58,11 @@ class Bot { const port = process.env.NOTIFICATION_PORT || 3000; await this.notificationService.start(port); console.log(`Notification service started on port ${port}`); - } catch (error) { + } catch (error) { console.error("Startup failed:", error); throw error; - } } +} async stop() { console.log("Stopping bot..."); diff --git a/src/commands/SubscriptionCommands.js b/src/commands/SubscriptionCommands.js index 81da935..205ba0c 100644 --- a/src/commands/SubscriptionCommands.js +++ b/src/commands/SubscriptionCommands.js @@ -1,62 +1,190 @@ +const { EmbedBuilder } = require('discord.js'); + class SubscriptionCommands { - constructor(supabaseService) { - this.supabaseService = supabaseService; + constructor(supabase) { + this.supabase = supabase; } - + async handleSubscribe(interaction) { - const gameName = interaction.options.getString('game'); - const channel = interaction.options.getChannel('channel') || interaction.channel; - try { - // Defer the reply immediately - await interaction.deferReply({ ephemeral: true }); - - // Perform the subscription operation - await this.supabaseService.addSubscription(interaction.guildId, gameName, channel.id); - - // Edit the deferred reply with the success message - await interaction.editReply(`Successfully subscribed to ${gameName} notifications in ${channel}.`); + const gameName = interaction.options.getString('game'); + const channel = interaction.options.getChannel('channel'); + const guildId = interaction.guildId; + + // First, get or create server record + const { data: serverData, error: serverError } = await this.supabase + .from('servers') + .upsert({ + discord_server_id: guildId, + server_name: interaction.guild.name + }, { + onConflict: 'discord_server_id', + returning: true + }); + + if (serverError) { + console.error('Server upsert error:', serverError); + await interaction.reply({ content: 'Failed to process server registration.', ephemeral: true }); + return; + } + + // Get game ID + const { data: gameData, error: gameError } = await this.supabase + .from('games') + .select('id') + .eq('name', gameName) + .eq('active', true) + .single(); + + if (gameError || !gameData) { + console.error('Game fetch error:', gameError); + await interaction.reply({ content: `Game "${gameName}" not found or not active.`, ephemeral: true }); + return; + } + + // Create subscription + const { error: prefError } = await this.supabase + .from('server_game_preferences') + .upsert({ + server_id: serverData[0].id, + game_id: gameData.id, + notification_channel_id: channel.id + }, { + onConflict: '(server_id,game_id)', + returning: true + }); + + if (prefError) { + console.error('Preference upsert error:', prefError); + await interaction.reply({ content: 'Failed to save subscription.', ephemeral: true }); + return; + } + + await interaction.reply({ + content: `Successfully subscribed to ${gameName} notifications in ${channel}.`, + ephemeral: true + }); + } catch (error) { - console.error('Error subscribing:', error); - - // If we failed to defer earlier, try to reply now - if (!interaction.deferred && !interaction.replied) { - await interaction.reply({ content: 'An error occurred while subscribing. Please try again.', ephemeral: true }).catch(console.error); - } else { - // If we successfully deferred earlier, edit the reply - await interaction.editReply('An error occurred while subscribing. Please try again.').catch(console.error); - } + console.error('Error subscribing:', error); + if (!interaction.replied && !interaction.deferred) { + await interaction.reply({ + content: 'An error occurred while processing your subscription.', + ephemeral: true + }); + } } - } - + } + async handleUnsubscribe(interaction) { - const gameName = interaction.options.getString('game'); - - try { - await this.supabaseService.removeSubscription(interaction.guildId, gameName); - await interaction.reply(`Successfully unsubscribed from ${gameName} notifications.`); - } catch (error) { - console.error('Error unsubscribing:', error); - await interaction.reply('An error occurred while unsubscribing. Please try again.'); - } - } - - async handleListSubscriptions(interaction) { - try { - const subscriptions = await this.supabaseService.getSubscriptions(interaction.guildId); - if (subscriptions.length === 0) { - await interaction.reply('This server has no active subscriptions.'); - } else { - const subscriptionList = subscriptions.map(sub => - `${sub.game_name} in <#${sub.channel_id}>` - ).join('\n'); - await interaction.reply(`Active subscriptions:\n${subscriptionList}`); + try { + const gameName = interaction.options.getString('game'); + const guildId = interaction.guildId; + + // Get server ID + const { data: serverData, error: serverError } = await this.supabase + .from('servers') + .select('id') + .eq('discord_server_id', guildId) + .single(); + + if (serverError) { + await interaction.reply({ content: 'Server not found.', ephemeral: true }); + return; + } + + // Get game ID + const { data: gameData, error: gameError } = await this.supabase + .from('games') + .select('id') + .eq('name', gameName) + .single(); + + if (gameError) { + await interaction.reply({ content: 'Game not found.', ephemeral: true }); + return; + } + + // Delete subscription + const { error: deleteError } = await this.supabase + .from('server_game_preferences') + .delete() + .eq('server_id', serverData.id) + .eq('game_id', gameData.id); + + if (deleteError) { + await interaction.reply({ content: 'Failed to remove subscription.', ephemeral: true }); + return; + } + + await interaction.reply({ + content: `Successfully unsubscribed from ${gameName} notifications.`, + ephemeral: true + }); + + } catch (error) { + console.error('Error unsubscribing:', error); + if (!interaction.replied && !interaction.deferred) { + await interaction.reply({ + content: 'An error occurred while processing your unsubscription.', + ephemeral: true + }); + } } - } catch (error) { - console.error('Error listing subscriptions:', error); - await interaction.reply('An error occurred while fetching subscriptions. Please try again.'); - } } - } - - module.exports = SubscriptionCommands; \ No newline at end of file + + async handleListSubscriptions(interaction) { + try { + const guildId = interaction.guildId; + + // Get all subscriptions for this server + const { data: subscriptions, error } = await this.supabase + .from('server_game_preferences') + .select(` + games (name), + notification_channel_id, + servers!inner (discord_server_id) + `) + .eq('servers.discord_server_id', guildId); + + if (error) { + await interaction.reply({ content: 'Failed to fetch subscriptions.', ephemeral: true }); + return; + } + + if (!subscriptions || subscriptions.length === 0) { + await interaction.reply({ + content: 'This server has no game subscriptions.', + ephemeral: true + }); + return; + } + + const embed = new EmbedBuilder() + .setTitle('Game Subscriptions') + .setDescription('Current game notification subscriptions for this server:') + .setColor('#0099ff'); + + subscriptions.forEach(sub => { + embed.addFields({ + name: sub.games.name, + value: `<#${sub.notification_channel_id}>`, + inline: true + }); + }); + + await interaction.reply({ embeds: [embed], ephemeral: true }); + + } catch (error) { + console.error('Error listing subscriptions:', error); + if (!interaction.replied && !interaction.deferred) { + await interaction.reply({ + content: 'An error occurred while fetching subscriptions.', + ephemeral: true + }); + } + } + } +} + +module.exports = SubscriptionCommands; \ No newline at end of file diff --git a/src/services/SupabaseService.js b/src/services/SupabaseService.js index f8a40d2..caae2ec 100644 --- a/src/services/SupabaseService.js +++ b/src/services/SupabaseService.js @@ -6,53 +6,142 @@ class SupabaseService { const supabaseKey = process.env.SUPABASE_KEY; if (!supabaseUrl || !supabaseKey) { - console.error('Supabase URL or key is missing. Please check your .env file.'); - this.supabase = null; + console.error('Supabase URL or key is missing. Please check your .env file.'); + this.supabase = null; } else { - this.supabase = createClient(supabaseUrl, supabaseKey); + this.supabase = createClient(supabaseUrl, supabaseKey); } - } - - async getSubscriptions(guildId) { - if (!this.supabase) { - console.error('Supabase client is not initialized.'); - return []; - } - const { data, error } = await this.supabase - .from('subscriptions') - .select('*') - .eq('guild_id', guildId); - - if (error) throw error; - return data; - } - - async addSubscription(guildId, gameName, channelId) { - if (!this.supabase) { - console.error('Supabase client is not initialized.'); - return null; - } - const { data, error } = await this.supabase - .from('subscriptions') - .insert({ guild_id: guildId, game_name: gameName, channel_id: channelId }); - - if (error) throw error; - return data; - } - - async removeSubscription(guildId, gameName) { - if (!this.supabase) { - console.error('Supabase client is not initialized.'); - return null; - } - const { data, error } = await this.supabase - .from('subscriptions') - .delete() - .match({ guild_id: guildId, game_name: gameName }); - - if (error) throw error; - return data; - } } + async testConnection() { + if (!this.supabase) { + console.error('Supabase client is not initialized.'); + return false; + } + + try { + const { data, error } = await this.supabase + .from('games') + .select('count') + .limit(1); + + if (error) { + console.error('Supabase connection test error:', error); + return false; + } + + console.log('Supabase connection successful'); + return true; + } catch (error) { + console.error('Supabase connection test failed:', error); + return false; + } + } + + async getSubscriptions(guildId) { + if (!this.supabase) { + console.error('Supabase client is not initialized.'); + return []; + } + + const { data, error } = await this.supabase + .from('server_game_preferences') + .select(` + games (name), + notification_channel_id, + servers!inner (discord_server_id) + `) + .eq('servers.discord_server_id', guildId); + + if (error) throw error; + return data; + } + + async addSubscription(guildId, gameName, channelId) { + if (!this.supabase) { + console.error('Supabase client is not initialized.'); + return null; + } + + // First, get or create server record + const { data: serverData, error: serverError } = await this.supabase + .from('servers') + .upsert({ + discord_server_id: guildId + }, { + onConflict: 'discord_server_id', + returning: true + }); + + if (serverError) throw serverError; + + // Get game ID + const { data: gameData, error: gameError } = await this.supabase + .from('games') + .select('id') + .eq('name', gameName) + .eq('active', true) + .single(); + + if (gameError) throw gameError; + + // Create subscription + const { data, error } = await this.supabase + .from('server_game_preferences') + .upsert({ + server_id: serverData[0].id, + game_id: gameData.id, + notification_channel_id: channelId + }, { + onConflict: '(server_id,game_id)', + returning: true + }); + + if (error) throw error; + return data; + } + + async removeSubscription(guildId, gameName) { + if (!this.supabase) { + console.error('Supabase client is not initialized.'); + return null; + } + + // Get server ID + const { data: serverData, error: serverError } = await this.supabase + .from('servers') + .select('id') + .eq('discord_server_id', guildId) + .single(); + + if (serverError) throw serverError; + + // Get game ID + const { data: gameData, error: gameError } = await this.supabase + .from('games') + .select('id') + .eq('name', gameName) + .single(); + + if (gameError) throw gameError; + + // Delete subscription + const { data, error } = await this.supabase + .from('server_game_preferences') + .delete() + .match({ + server_id: serverData.id, + game_id: gameData.id + }) + .single(); + + if (error) throw error; + return data; + } + + getClient() { + return this.supabase; + } +} + module.exports = SupabaseService; \ No newline at end of file