From be9df4aa854c8273d959bf2884bd64605015e651 Mon Sep 17 00:00:00 2001 From: Till-JS <101404291+Till-JS@users.noreply.github.com> Date: Tue, 25 Nov 2025 02:49:34 +0100 Subject: [PATCH] feat(manadeck): complete Supabase removal from frontend and backend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Frontend: Updated deckStore to use fetch API calls to backend - Frontend: Removed supabase.ts utility and @supabase/supabase-js dependency - Backend: Removed SupabaseService and supabase.service.ts - Backend: Updated validation schema to use DATABASE_URL - Backend: Updated health controller to remove Supabase check - Backend: Marked AI generation endpoint as not implemented (was using Edge Functions) Phase 4 of PostgreSQL/Drizzle migration complete. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- manadeck/apps/web/package.json | 2 - .../web/src/lib/stores/deckStore.svelte.ts | 171 +++++------ manadeck/apps/web/src/lib/utils/supabase.ts | 44 --- manadeck/backend/package.json | 1 - manadeck/backend/src/app.module.ts | 2 - .../backend/src/config/validation.schema.ts | 6 +- .../backend/src/controllers/api.controller.ts | 126 +------- .../src/controllers/health.controller.ts | 3 +- manadeck/backend/src/main.ts | 2 +- .../backend/src/services/supabase.service.ts | 283 ------------------ 10 files changed, 87 insertions(+), 553 deletions(-) delete mode 100644 manadeck/apps/web/src/lib/utils/supabase.ts delete mode 100644 manadeck/backend/src/services/supabase.service.ts diff --git a/manadeck/apps/web/package.json b/manadeck/apps/web/package.json index d424af6cf..7fdb1c8dd 100644 --- a/manadeck/apps/web/package.json +++ b/manadeck/apps/web/package.json @@ -34,14 +34,12 @@ "@manacore/shared-icons": "workspace:*", "@manacore/shared-subscription-types": "workspace:*", "@manacore/shared-subscription-ui": "workspace:*", - "@manacore/shared-supabase": "workspace:*", "@manacore/shared-tailwind": "workspace:*", "@manacore/shared-theme": "workspace:*", "@manacore/shared-theme-ui": "workspace:*", "@manacore/shared-types": "workspace:*", "@manacore/shared-ui": "workspace:*", "@manacore/shared-utils": "workspace:*", - "@supabase/supabase-js": "^2.81.1", "svelte-i18n": "^4.0.1" } } diff --git a/manadeck/apps/web/src/lib/stores/deckStore.svelte.ts b/manadeck/apps/web/src/lib/stores/deckStore.svelte.ts index 235f1d7ea..07199a5c6 100644 --- a/manadeck/apps/web/src/lib/stores/deckStore.svelte.ts +++ b/manadeck/apps/web/src/lib/stores/deckStore.svelte.ts @@ -1,5 +1,5 @@ import type { Deck, CreateDeckInput, UpdateDeckInput } from '$lib/types/deck'; -import { getAuthenticatedSupabase } from '$lib/utils/supabase'; +import { PUBLIC_API_URL } from '$env/static/public'; import { authService } from '$lib/auth'; // Svelte 5 runes-based deck store @@ -8,6 +8,35 @@ let currentDeck = $state(null); let loading = $state(false); let error = $state(null); +/** + * Helper to make authenticated API requests + */ +async function apiRequest( + endpoint: string, + options: RequestInit = {} +): Promise { + const appToken = await authService.getAppToken(); + if (!appToken) { + throw new Error('Not authenticated'); + } + + const response = await fetch(`${PUBLIC_API_URL}${endpoint}`, { + ...options, + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${appToken}`, + ...options.headers + } + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error(errorData.message || `API error: ${response.status}`); + } + + return response.json(); +} + export const deckStore = { get decks() { return decks; @@ -30,31 +59,8 @@ export const deckStore = { error = null; try { - const appToken = await authService.getAppToken(); - if (!appToken) { - throw new Error('Not authenticated'); - } - - const user = await authService.getUserFromToken(); - if (!user) { - throw new Error('No user found'); - } - - const supabase = await getAuthenticatedSupabase(appToken); - - const { data, error: fetchError } = await supabase - .from('decks') - .select('*, cards(count)') - .or(`user_id.eq.${user.id},and(is_public.eq.true,user_id.eq.00000000-0000-0000-0000-000000000001)`) - .order('updated_at', { ascending: false }); - - if (fetchError) throw fetchError; - - // Map card count - decks = (data || []).map((deck: any) => ({ - ...deck, - card_count: deck.cards?.[0]?.count || 0 - })); + const response = await apiRequest<{ decks: Deck[]; count: number }>('/v1/api/decks'); + decks = response.decks || []; } catch (err: any) { error = err.message || 'Failed to fetch decks'; console.error('Fetch decks error:', err); @@ -71,23 +77,15 @@ export const deckStore = { error = null; try { - const appToken = await authService.getAppToken(); - if (!appToken) throw new Error('Not authenticated'); - - const supabase = await getAuthenticatedSupabase(appToken); - - const { data, error: fetchError } = await supabase - .from('decks') - .select('*, cards(count)') - .eq('id', id) - .single(); - - if (fetchError) throw fetchError; - - currentDeck = { - ...data, - card_count: data.cards?.[0]?.count || 0 - }; + // For now, find in local decks or fetch all + // TODO: Add GET /v1/api/decks/:id endpoint to backend + if (decks.length === 0) { + await this.fetchDecks(); + } + currentDeck = decks.find((d) => d.id === id) || null; + if (!currentDeck) { + throw new Error('Deck not found'); + } } catch (err: any) { error = err.message || 'Failed to fetch deck'; console.error('Fetch deck error:', err); @@ -104,35 +102,23 @@ export const deckStore = { error = null; try { - const appToken = await authService.getAppToken(); - if (!appToken) throw new Error('Not authenticated'); + const response = await apiRequest<{ success: boolean; deck: Deck }>('/v1/api/decks', { + method: 'POST', + body: JSON.stringify({ + title: input.title, + description: input.description || '', + isPublic: input.is_public ?? false, + tags: input.tags || [], + settings: input.settings || {} + }) + }); - const user = await authService.getUserFromToken(); - if (!user) throw new Error('No user found'); - - const supabase = await getAuthenticatedSupabase(appToken); - - const newDeck = { - user_id: user.id, - title: input.title, - description: input.description || '', - is_public: input.is_public ?? false, - tags: input.tags || [], - settings: input.settings || {}, - metadata: {} - }; - - const { data, error: createError } = await supabase - .from('decks') - .insert(newDeck) - .select() - .single(); - - if (createError) throw createError; - - const deck = { ...data, card_count: 0 }; - decks = [deck, ...decks]; - return deck; + if (response.deck) { + const deck = { ...response.deck, card_count: 0 }; + decks = [deck, ...decks]; + return deck; + } + return null; } catch (err: any) { error = err.message || 'Failed to create deck'; console.error('Create deck error:', err); @@ -150,26 +136,22 @@ export const deckStore = { error = null; try { - const appToken = await authService.getAppToken(); - if (!appToken) throw new Error('Not authenticated'); + const response = await apiRequest<{ success: boolean; deck: Deck }>( + `/v1/api/decks/${id}`, + { + method: 'PUT', + body: JSON.stringify(updates) + } + ); - const supabase = await getAuthenticatedSupabase(appToken); + if (response.deck) { + // Update in list + decks = decks.map((d) => (d.id === id ? { ...d, ...response.deck } : d)); - const { data, error: updateError } = await supabase - .from('decks') - .update(updates) - .eq('id', id) - .select() - .single(); - - if (updateError) throw updateError; - - // Update in list - decks = decks.map((d) => (d.id === id ? { ...d, ...data } : d)); - - // Update current if it's the same - if (currentDeck?.id === id) { - currentDeck = { ...currentDeck, ...data }; + // Update current if it's the same + if (currentDeck?.id === id) { + currentDeck = { ...currentDeck, ...response.deck }; + } } } catch (err: any) { error = err.message || 'Failed to update deck'; @@ -187,14 +169,9 @@ export const deckStore = { error = null; try { - const appToken = await authService.getAppToken(); - if (!appToken) throw new Error('Not authenticated'); - - const supabase = await getAuthenticatedSupabase(appToken); - - const { error: deleteError } = await supabase.from('decks').delete().eq('id', id); - - if (deleteError) throw deleteError; + await apiRequest<{ success: boolean }>(`/v1/api/decks/${id}`, { + method: 'DELETE' + }); // Remove from list decks = decks.filter((d) => d.id !== id); diff --git a/manadeck/apps/web/src/lib/utils/supabase.ts b/manadeck/apps/web/src/lib/utils/supabase.ts deleted file mode 100644 index 1e2893cce..000000000 --- a/manadeck/apps/web/src/lib/utils/supabase.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { createClient } from '@supabase/supabase-js'; -import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'; - -// Create a singleton Supabase client -export const supabase = createClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, { - auth: { - autoRefreshToken: false, - persistSession: false, - detectSessionInUrl: false - } -}); - -/** - * Get authenticated Supabase client with Mana Core token - * This injects the JWT token from Mana Core for RLS policies - */ -export async function getAuthenticatedSupabase(appToken?: string) { - if (!appToken) { - // Try to get token from sessionStorage - if (typeof window !== 'undefined') { - appToken = sessionStorage.getItem('appToken') || undefined; - } - } - - if (!appToken) { - throw new Error('No auth token available'); - } - - // Create a new client instance with the auth token - const authenticatedClient = createClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, { - global: { - headers: { - Authorization: `Bearer ${appToken}` - } - }, - auth: { - autoRefreshToken: false, - persistSession: false, - detectSessionInUrl: false - } - }); - - return authenticatedClient; -} diff --git a/manadeck/backend/package.json b/manadeck/backend/package.json index fc64e9f10..1986dd57d 100644 --- a/manadeck/backend/package.json +++ b/manadeck/backend/package.json @@ -28,7 +28,6 @@ "@nestjs/core": "^11.0.1", "@nestjs/platform-express": "^11.0.1", "@nestjs/terminus": "^11.0.0", - "@supabase/supabase-js": "^2.81.1", "axios": "^1.7.2", "class-transformer": "^0.5.1", "class-validator": "^0.14.2", diff --git a/manadeck/backend/src/app.module.ts b/manadeck/backend/src/app.module.ts index 6c844d7cf..714392002 100644 --- a/manadeck/backend/src/app.module.ts +++ b/manadeck/backend/src/app.module.ts @@ -9,7 +9,6 @@ import { AppService } from './app.service'; import { ApiController } from './controllers/api.controller'; import { PublicController } from './controllers/public.controller'; import { HealthController } from './controllers/health.controller'; -import { SupabaseService } from './services/supabase.service'; import { validationSchema } from './config/validation.schema'; import { DatabaseModule, @@ -65,7 +64,6 @@ import { ], providers: [ AppService, - SupabaseService, // Database repositories DeckRepository, CardRepository, diff --git a/manadeck/backend/src/config/validation.schema.ts b/manadeck/backend/src/config/validation.schema.ts index 4b44d3416..94bcb1c65 100644 --- a/manadeck/backend/src/config/validation.schema.ts +++ b/manadeck/backend/src/config/validation.schema.ts @@ -12,10 +12,8 @@ export const validationSchema = Joi.object({ MANA_SUPABASE_SECRET_KEY: Joi.string().optional(), SIGNUP_REDIRECT_URL: Joi.string().uri().optional(), - // Your app's database - SUPABASE_URL: Joi.string().uri().required(), - SUPABASE_ANON_KEY: Joi.string().required(), - SUPABASE_SERVICE_KEY: Joi.string().required(), // Required for edge functions + // PostgreSQL Database + DATABASE_URL: Joi.string().required(), // JWT JWT_SECRET: Joi.string().optional(), diff --git a/manadeck/backend/src/controllers/api.controller.ts b/manadeck/backend/src/controllers/api.controller.ts index 4fd59dcf1..c2d452965 100644 --- a/manadeck/backend/src/controllers/api.controller.ts +++ b/manadeck/backend/src/controllers/api.controller.ts @@ -1,9 +1,8 @@ -import { Controller, Get, Post, Put, Delete, Body, Param, UseGuards, Logger, BadRequestException, Req } from '@nestjs/common'; +import { Controller, Get, Post, Put, Delete, Body, Param, UseGuards, Logger, BadRequestException, NotImplementedException } from '@nestjs/common'; import { AuthGuard } from '@mana-core/nestjs-integration/guards'; import { CurrentUser } from '@mana-core/nestjs-integration/decorators'; import { CreditClientService } from '@mana-core/nestjs-integration'; import { CreditOperationType, getCreditCost, getOperationDescription } from '../config/credit-operations'; -import { SupabaseService } from '../services/supabase.service'; import { DeckRepository, CardRepository, UserStatsRepository } from '../database'; @Controller('api') @@ -13,7 +12,6 @@ export class ApiController { constructor( private readonly creditClient: CreditClientService, - private readonly supabaseService: SupabaseService, private readonly deckRepository: DeckRepository, private readonly cardRepository: CardRepository, private readonly userStatsRepository: UserStatsRepository, @@ -150,121 +148,15 @@ export class ApiController { } @Post('decks/generate') - async generateDeckWithAI(@CurrentUser() user: any, @Body() requestData: any, @Req() req: any) { - this.logger.log(`Generating AI deck for user: ${user.sub}`); + async generateDeckWithAI(@CurrentUser() user: any, @Body() requestData: any) { + this.logger.log(`AI deck generation requested by user: ${user.sub}`); - const { prompt, deckTitle, deckDescription, cardCount = 10, cardTypes, difficulty, tags } = requestData; - - // Validate required fields - if (!prompt || !deckTitle) { - throw new BadRequestException({ - error: 'validation_failed', - message: 'prompt and deckTitle are required', - }); - } - - if (cardCount < 1 || cardCount > 50) { - throw new BadRequestException({ - error: 'validation_failed', - message: 'cardCount must be between 1 and 50', - }); - } - - const operationType = CreditOperationType.AI_DECK_GENERATION; - const creditCost = getCreditCost(operationType); - - try { - // 1. Pre-flight credit validation - const validation = await this.creditClient.validateCredits( - user.sub, - operationType, - creditCost, - ); - - if (!validation.hasCredits) { - this.logger.warn( - `User ${user.sub} has insufficient credits for AI deck generation. Required: ${creditCost}, Available: ${validation.availableCredits}`, - ); - - throw new BadRequestException({ - error: 'insufficient_credits', - message: `Insufficient mana. Required: ${creditCost}, Available: ${validation.availableCredits}`, - requiredCredits: creditCost, - availableCredits: validation.availableCredits, - operation: getOperationDescription(operationType), - }); - } - - // 2. Get the Mana token from the request - const authHeader = req.headers.authorization; - if (!authHeader) { - throw new BadRequestException({ - error: 'authentication_failed', - message: 'No authorization token found', - }); - } - const manaToken = authHeader.replace('Bearer ', ''); - - // 3. Call the edge function via Supabase - const result = await this.supabaseService.generateDeckWithAI( - user.sub, - { - prompt, - deckTitle, - deckDescription, - cardCount, - cardTypes, - difficulty, - tags, - }, - manaToken, - ); - - // 4. Check if the edge function was successful - if (!result.success) { - throw new BadRequestException({ - error: 'deck_generation_failed', - message: result.error || 'Failed to generate deck', - }); - } - - // 5. Success - Consume credits - await this.creditClient.consumeCredits( - user.sub, - operationType, - creditCost, - `Generated AI deck: ${deckTitle}`, - { - deckId: result.deck?.id, - deckTitle, - cardCount: result.deck?.card_count, - prompt, - }, - ); - - this.logger.log(`AI deck generated successfully for user ${user.sub}. ${creditCost} credits consumed.`); - - return { - success: true, - userId: user.sub, - deck: result.deck, - cards: result.cards, - creditsUsed: creditCost, - message: result.message || 'Deck generated successfully with AI', - }; - } catch (error) { - // If it's already a BadRequestException, rethrow it - if (error instanceof BadRequestException) { - throw error; - } - - // Log other errors - this.logger.error(`Error generating AI deck for user ${user.sub}:`, error); - throw new BadRequestException({ - error: 'deck_generation_failed', - message: error.message || 'Failed to generate deck with AI', - }); - } + // TODO: Implement AI deck generation with a self-hosted solution + // This endpoint previously used Supabase Edge Functions which are no longer available + throw new NotImplementedException({ + error: 'not_implemented', + message: 'AI deck generation is currently being migrated to a new infrastructure. Please check back later.', + }); } @Put('decks/:id') diff --git a/manadeck/backend/src/controllers/health.controller.ts b/manadeck/backend/src/controllers/health.controller.ts index 1d1a9e4aa..0eae1454d 100644 --- a/manadeck/backend/src/controllers/health.controller.ts +++ b/manadeck/backend/src/controllers/health.controller.ts @@ -20,11 +20,10 @@ export class HealthController { @HealthCheck() check() { const manaServiceUrl = this.configService.get('MANA_SERVICE_URL')!; - const supabaseUrl = this.configService.get('SUPABASE_URL')!; return this.health.check([ () => this.http.pingCheck('mana-core', manaServiceUrl), - () => this.http.pingCheck('supabase', `${supabaseUrl}/rest/v1/`), + // PostgreSQL health is checked via database module initialization ]); } diff --git a/manadeck/backend/src/main.ts b/manadeck/backend/src/main.ts index dc7e4f0f8..4d6c47a09 100644 --- a/manadeck/backend/src/main.ts +++ b/manadeck/backend/src/main.ts @@ -11,7 +11,7 @@ async function bootstrap() { // Debug: Log environment variables before validation logger.log('=== Environment Variables Debug ==='); logger.log(`APP_ID: ${process.env.APP_ID ? process.env.APP_ID.substring(0, 20) + '...' : 'NOT SET'}`); - logger.log(`SUPABASE_URL: ${process.env.SUPABASE_URL || 'NOT SET'}`); + logger.log(`DATABASE_URL: ${process.env.DATABASE_URL ? '[SET]' : 'NOT SET'}`); logger.log(`MANA_SERVICE_URL: ${process.env.MANA_SERVICE_URL || 'NOT SET'}`); logger.log('==================================='); diff --git a/manadeck/backend/src/services/supabase.service.ts b/manadeck/backend/src/services/supabase.service.ts deleted file mode 100644 index bfaef695e..000000000 --- a/manadeck/backend/src/services/supabase.service.ts +++ /dev/null @@ -1,283 +0,0 @@ -import { Injectable, Logger } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; -import { createClient, SupabaseClient } from '@supabase/supabase-js'; - -@Injectable() -export class SupabaseService { - private readonly logger = new Logger(SupabaseService.name); - private supabase: SupabaseClient; - private supabaseServiceRole: SupabaseClient; - - constructor(private configService: ConfigService) { - const supabaseUrl = this.configService.get('SUPABASE_URL')!; - const supabaseAnonKey = this.configService.get('SUPABASE_ANON_KEY')!; - const supabaseServiceKey = this.configService.get('SUPABASE_SERVICE_KEY'); - - // Client for public operations - this.supabase = createClient(supabaseUrl, supabaseAnonKey, { - auth: { - autoRefreshToken: false, - persistSession: false, - detectSessionInUrl: false, - }, - }); - - // Client for service-level operations (optional, only if service key is provided) - if (supabaseServiceKey) { - this.supabaseServiceRole = createClient(supabaseUrl, supabaseServiceKey, { - auth: { - autoRefreshToken: false, - persistSession: false, - detectSessionInUrl: false, - }, - }); - } - - this.logger.log('Supabase service initialized'); - } - - // Get client with user's token for RLS - getClientWithUserToken(token: string): SupabaseClient { - const supabaseUrl = this.configService.get('SUPABASE_URL')!; - const supabaseAnonKey = this.configService.get('SUPABASE_ANON_KEY')!; - - return createClient(supabaseUrl, supabaseAnonKey, { - global: { - headers: { - Authorization: `Bearer ${token}`, - }, - }, - auth: { - autoRefreshToken: false, - persistSession: false, - detectSessionInUrl: false, - }, - }); - } - - // Example methods for deck operations - async getUserDecks(userId: string, token?: string) { - const client = token ? this.getClientWithUserToken(token) : this.supabase; - - const { data, error } = await client - .from('decks') - .select('*') - .eq('user_id', userId) - .order('created_at', { ascending: false }); - - if (error) { - this.logger.error('Error fetching user decks:', error); - throw error; - } - - return data; - } - - async createDeck(userId: string, deckData: any, token?: string) { - const client = token ? this.getClientWithUserToken(token) : this.supabase; - - const { data, error } = await client - .from('decks') - .insert({ - ...deckData, - user_id: userId, - created_at: new Date(), - updated_at: new Date(), - }) - .select() - .single(); - - if (error) { - this.logger.error('Error creating deck:', error); - throw error; - } - - return data; - } - - async updateDeck(deckId: string, userId: string, deckData: any, token?: string) { - const client = token ? this.getClientWithUserToken(token) : this.supabase; - - const { data, error } = await client - .from('decks') - .update({ - ...deckData, - updated_at: new Date(), - }) - .eq('id', deckId) - .eq('user_id', userId) // Ensure user owns the deck - .select() - .single(); - - if (error) { - this.logger.error('Error updating deck:', error); - throw error; - } - - return data; - } - - async deleteDeck(deckId: string, userId: string, token?: string) { - const client = token ? this.getClientWithUserToken(token) : this.supabase; - - const { error } = await client - .from('decks') - .delete() - .eq('id', deckId) - .eq('user_id', userId); // Ensure user owns the deck - - if (error) { - this.logger.error('Error deleting deck:', error); - throw error; - } - - return { success: true }; - } - - // Example methods for card operations - async getUserCards(userId: string, token?: string) { - const client = token ? this.getClientWithUserToken(token) : this.supabase; - - const { data, error } = await client - .from('cards') - .select('*') - .eq('user_id', userId) - .order('created_at', { ascending: false }); - - if (error) { - this.logger.error('Error fetching user cards:', error); - throw error; - } - - return data; - } - - async createCard(userId: string, cardData: any, token?: string) { - const client = token ? this.getClientWithUserToken(token) : this.supabase; - - const { data, error } = await client - .from('cards') - .insert({ - ...cardData, - user_id: userId, - created_at: new Date(), - updated_at: new Date(), - }) - .select() - .single(); - - if (error) { - this.logger.error('Error creating card:', error); - throw error; - } - - return data; - } - - // Public methods (no auth required) - async getFeaturedDecks(limit = 10) { - const { data, error } = await this.supabase - .from('decks') - .select('*') - .eq('is_featured', true) - .eq('is_public', true) - .limit(limit) - .order('featured_at', { ascending: false }); - - if (error) { - this.logger.error('Error fetching featured decks:', error); - throw error; - } - - return data; - } - - async getLeaderboard(limit = 10) { - const { data, error } = await this.supabase - .from('user_stats') - .select('*') - .order('total_wins', { ascending: false }) - .limit(limit); - - if (error) { - this.logger.error('Error fetching leaderboard:', error); - throw error; - } - - return data; - } - - async getDeckTemplates(category?: string) { - let query = this.supabase - .from('deck_templates') - .select('*') - .eq('is_active', true); - - if (category) { - query = query.eq('category', category); - } - - const { data, error } = await query.order('popularity', { ascending: false }); - - if (error) { - this.logger.error('Error fetching deck templates:', error); - throw error; - } - - return data; - } - - // Service-level operations (using service role key) - async adminGetAllUsers() { - if (!this.supabaseServiceRole) { - throw new Error('Service role key not configured'); - } - - const { data, error } = await this.supabaseServiceRole.auth.admin.listUsers(); - - if (error) { - this.logger.error('Error fetching all users:', error); - throw error; - } - - return data; - } - - // Edge Function invocations - async generateDeckWithAI( - userId: string, - requestData: { - prompt: string; - deckTitle: string; - deckDescription?: string; - cardCount?: number; - cardTypes?: string[]; - difficulty?: string; - tags?: string[]; - }, - manaToken: string, - ) { - if (!this.supabaseServiceRole) { - throw new Error('Service role key not configured'); - } - - this.logger.log(`Invoking generate-deck edge function for user ${userId}`); - - const { data, error } = await this.supabaseServiceRole.functions.invoke( - 'generate-deck', - { - body: requestData, - headers: { - Authorization: `Bearer ${manaToken}`, - }, - }, - ); - - if (error) { - this.logger.error('Error invoking generate-deck edge function:', error); - throw error; - } - - return data; - } -} \ No newline at end of file