supabase setup

This commit is contained in:
VinceC
2024-11-24 03:49:23 -06:00
parent c62c1f81e4
commit 3b10e77c82
3 changed files with 330 additions and 103 deletions

View File

@@ -38,9 +38,19 @@ class Bot {
this.client.on('interactionCreate', this.handleInteraction.bind(this)); this.client.on('interactionCreate', this.handleInteraction.bind(this));
} }
async start(token) { // In the start method, add Supabase connection test:
console.log("Starting bot..."); async start(token) {
try { 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); await this.client.login(token);
console.log("Login successful"); console.log("Login successful");
@@ -48,11 +58,11 @@ class Bot {
const port = process.env.NOTIFICATION_PORT || 3000; const port = process.env.NOTIFICATION_PORT || 3000;
await this.notificationService.start(port); await this.notificationService.start(port);
console.log(`Notification service started on port ${port}`); console.log(`Notification service started on port ${port}`);
} catch (error) { } catch (error) {
console.error("Startup failed:", error); console.error("Startup failed:", error);
throw error; throw error;
}
} }
}
async stop() { async stop() {
console.log("Stopping bot..."); console.log("Stopping bot...");

View File

@@ -1,62 +1,190 @@
const { EmbedBuilder } = require('discord.js');
class SubscriptionCommands { class SubscriptionCommands {
constructor(supabaseService) { constructor(supabase) {
this.supabaseService = supabaseService; this.supabase = supabase;
} }
async handleSubscribe(interaction) { async handleSubscribe(interaction) {
const gameName = interaction.options.getString('game');
const channel = interaction.options.getChannel('channel') || interaction.channel;
try { try {
// Defer the reply immediately const gameName = interaction.options.getString('game');
await interaction.deferReply({ ephemeral: true }); const channel = interaction.options.getChannel('channel');
const guildId = interaction.guildId;
// Perform the subscription operation
await this.supabaseService.addSubscription(interaction.guildId, gameName, channel.id); // First, get or create server record
const { data: serverData, error: serverError } = await this.supabase
// Edit the deferred reply with the success message .from('servers')
await interaction.editReply(`Successfully subscribed to ${gameName} notifications in ${channel}.`); .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) { } catch (error) {
console.error('Error subscribing:', error); console.error('Error subscribing:', error);
if (!interaction.replied && !interaction.deferred) {
// If we failed to defer earlier, try to reply now await interaction.reply({
if (!interaction.deferred && !interaction.replied) { content: 'An error occurred while processing your subscription.',
await interaction.reply({ content: 'An error occurred while subscribing. Please try again.', ephemeral: true }).catch(console.error); ephemeral: true
} else { });
// If we successfully deferred earlier, edit the reply }
await interaction.editReply('An error occurred while subscribing. Please try again.').catch(console.error);
}
} }
} }
async handleUnsubscribe(interaction) { async handleUnsubscribe(interaction) {
const gameName = interaction.options.getString('game'); try {
const gameName = interaction.options.getString('game');
try { const guildId = interaction.guildId;
await this.supabaseService.removeSubscription(interaction.guildId, gameName);
await interaction.reply(`Successfully unsubscribed from ${gameName} notifications.`); // Get server ID
} catch (error) { const { data: serverData, error: serverError } = await this.supabase
console.error('Error unsubscribing:', error); .from('servers')
await interaction.reply('An error occurred while unsubscribing. Please try again.'); .select('id')
} .eq('discord_server_id', guildId)
} .single();
async handleListSubscriptions(interaction) { if (serverError) {
try { await interaction.reply({ content: 'Server not found.', ephemeral: true });
const subscriptions = await this.supabaseService.getSubscriptions(interaction.guildId); return;
if (subscriptions.length === 0) { }
await interaction.reply('This server has no active subscriptions.');
} else { // Get game ID
const subscriptionList = subscriptions.map(sub => const { data: gameData, error: gameError } = await this.supabase
`${sub.game_name} in <#${sub.channel_id}>` .from('games')
).join('\n'); .select('id')
await interaction.reply(`Active subscriptions:\n${subscriptionList}`); .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.');
}
} }
}
async handleListSubscriptions(interaction) {
module.exports = SubscriptionCommands; 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;

View File

@@ -6,53 +6,142 @@ class SupabaseService {
const supabaseKey = process.env.SUPABASE_KEY; const supabaseKey = process.env.SUPABASE_KEY;
if (!supabaseUrl || !supabaseKey) { if (!supabaseUrl || !supabaseKey) {
console.error('Supabase URL or key is missing. Please check your .env file.'); console.error('Supabase URL or key is missing. Please check your .env file.');
this.supabase = null; this.supabase = null;
} else { } 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; module.exports = SupabaseService;