const { createClient } = require('@supabase/supabase-js'); const Logger = require('../utils/Logger'); class SupabaseService { constructor() { this.logger = new Logger('SupabaseService'); const supabaseUrl = process.env.SUPABASE_URL; const supabaseKey = process.env.SUPABASE_KEY; if (!supabaseUrl || !supabaseKey) { this.logger.error('Missing configuration', { hasUrl: !!supabaseUrl, hasKey: !!supabaseKey }); this.supabase = null; } else { this.supabase = createClient(supabaseUrl, supabaseKey, { auth: { autoRefreshToken: false, persistSession: false }, global: { headers: { 'apikey': supabaseKey } } }); this.logger.debug('Supabase client created'); } } async testConnection() { if (!this.supabase) { this.logger.error('Client not initialized'); return false; } try { this.logger.debug('Testing database connection...'); // Test servers table const { data: serverTest, error: serverError } = await this.supabase .from('servers') .select('count') .limit(1); if (serverError) { this.logger.error('Server table test failed:', serverError); return false; } // Test games table const { data: gameTest, error: gameError } = await this.supabase .from('games') .select('count') .limit(1); if (gameError) { this.logger.error('Games table test failed:', gameError); return false; } this.logger.info('Database connection successful'); return true; } catch (error) { this.logger.error('Connection test failed:', { error: error.message, stack: error.stack }); return false; } } async ensureServerRegistered(guildId, serverName) { try { this.logger.debug('Ensuring server is registered', { guildId, serverName }); // Try to find existing server const { data: existingServer, error: fetchError } = await this.supabase .from('servers') .select('*') .eq('discord_server_id', guildId) .single(); // If server exists, return it if (existingServer) { this.logger.debug('Server already registered', { serverId: existingServer.id, guildId, serverName: existingServer.server_name }); return existingServer; } // If server doesn't exist, create it this.logger.debug('Registering new server', { guildId, serverName }); const { data: newServer, error: insertError } = await this.supabase .from('servers') .insert([{ discord_server_id: guildId, server_name: serverName, active: true }]) .select() .single(); if (insertError) { this.logger.error('Failed to register server', { error: insertError, guildId, serverName }); throw insertError; } this.logger.info('New server registered successfully', { serverId: newServer.id, guildId, serverName }); return newServer; } catch (error) { this.logger.error('Error in ensureServerRegistered:', { error: error.message, stack: error.stack, guildId, serverName }); throw error; } } // Update the addSubscription method to use ensureServerRegistered async addSubscription(guildId, gameName, channelId, serverName) { if (!this.supabase) { this.logger.error('Client not initialized'); return null; } try { this.logger.debug('Adding subscription', { guildId, gameName, channelId }); // Ensure server is registered const server = await this.ensureServerRegistered(guildId, serverName); if (!server) { throw new Error('Failed to ensure server registration'); } // Get game ID const { data: gameData, error: gameError } = await this.supabase .from('games') .select('id') .eq('name', gameName) .eq('active', true) .single(); if (gameError) { this.logger.error('Game fetch error:', { gameError, gameName }); throw gameError; } // Create subscription const { data, error } = await this.supabase .from('server_game_preferences') .upsert({ server_id: server.id, game_id: gameData.id, notification_channel_id: channelId }, { onConflict: '(server_id,game_id)', returning: true }); if (error) { this.logger.error('Subscription upsert error:', { error }); throw error; } this.logger.debug('Subscription added successfully', { serverId: server.id, gameId: gameData.id, channelId }); return data; } catch (error) { this.logger.error('Error in addSubscription:', { error: error.message, stack: error.stack, guildId, gameName }); throw error; } } async getSubscriptions(guildId) { if (!this.supabase) { this.logger.error('Client not initialized'); return []; } try { this.logger.debug('Fetching subscriptions', { guildId }); 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) { this.logger.error('Error fetching subscriptions:', { error, guildId }); throw error; } this.logger.debug('Subscriptions fetched', { count: data?.length || 0, guildId }); return data || []; } catch (error) { this.logger.error('Error in getSubscriptions:', { error: error.message, stack: error.stack, guildId }); throw error; } } async addSubscription(guildId, gameName, channelId) { if (!this.supabase) { this.logger.error('Client not initialized'); return null; } try { this.logger.debug('Adding subscription', { guildId, gameName, channelId }); // First, get or create server record const { data: serverData, error: serverError } = await this.supabase .from('servers') .upsert({ discord_server_id: guildId, active: true }, { onConflict: 'discord_server_id', returning: true }); if (serverError) { this.logger.error('Server upsert error:', { serverError, guildId }); throw serverError; } if (!serverData || serverData.length === 0) { this.logger.error('Server creation failed - no data returned', { guildId }); throw new Error('Server creation failed'); } // Get game ID const { data: gameData, error: gameError } = await this.supabase .from('games') .select('id') .eq('name', gameName) .eq('active', true) .single(); if (gameError) { this.logger.error('Game fetch error:', { gameError, gameName }); 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) { this.logger.error('Subscription upsert error:', { error }); throw error; } this.logger.debug('Subscription added successfully', { serverId: serverData[0].id, gameId: gameData.id, channelId }); return data; } catch (error) { this.logger.error('Error in addSubscription:', { error: error.message, stack: error.stack, guildId, gameName }); throw error; } } async removeSubscription(guildId, gameName) { if (!this.supabase) { this.logger.error('Client not initialized'); return null; } try { this.logger.debug('Removing subscription', { guildId, gameName }); // Get server ID const { data: serverData, error: serverError } = await this.supabase .from('servers') .select('id') .eq('discord_server_id', guildId) .single(); if (serverError) { this.logger.error('Server fetch error:', { serverError, guildId }); throw serverError; } // Get game ID const { data: gameData, error: gameError } = await this.supabase .from('games') .select('id') .eq('name', gameName) .single(); if (gameError) { this.logger.error('Game fetch error:', { gameError, gameName }); 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) { this.logger.error('Subscription deletion error:', { error }); throw error; } this.logger.debug('Subscription removed successfully', { serverId: serverData.id, gameId: gameData.id }); return data; } catch (error) { this.logger.error('Error in removeSubscription:', { error: error.message, stack: error.stack, guildId, gameName }); throw error; } } getClient() { return this.supabase; } } module.exports = SupabaseService;