managarten/games/figgos/apps/mobile/utils/figureService.ts
Till-JS 05d074c57e 🔧 refactor(figgos): restructure to standard monorepo pattern
Migrate figgos from single Expo app to multi-app monorepo structure:
- Move mobile app to apps/mobile/
- Add apps/web/ (SvelteKit) and apps/backend/ (NestJS) scaffolds
- Add packages/shared/ for shared types and constants
- Update root package.json with new dev commands
- Temporarily skip type-check (run pnpm install first)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 17:27:15 +01:00

444 lines
14 KiB
TypeScript

import { supabase } from './supabase';
import * as FileSystem from 'expo-file-system';
import { ExtendedFigureData } from '../components/CreateFigureForm';
/**
* Generates a figure using the Edge Function and stores it in the database
*/
export async function generateFigure(formData: ExtendedFigureData, isPublic: boolean = true) {
try {
// Convert image to Base64 if available
let faceImageBase64 = null;
if (formData.characterImage) {
// For web
if (formData.characterImage.startsWith('data:')) {
faceImageBase64 = formData.characterImage.split(',')[1];
}
// For native platforms
else {
const base64 = await FileSystem.readAsStringAsync(formData.characterImage, {
encoding: FileSystem.EncodingType.Base64,
});
faceImageBase64 = base64;
}
}
// Prepare payload for the Edge Function with the new JSONB structure
// Die Edge-Funktion wird die vollständige JSONB-Struktur generieren, wenn Felder fehlen
const payload = {
subject: formData.name,
rarity: formData.rarity || 'common',
face_image: faceImageBase64,
// Wir können optional eine vordefinierte JSONB-Struktur mitgeben, wenn wir bestimmte Werte haben
character_info: {
character: {
image_prompt: formData.characterDescription || '',
// Diese Felder werden von der Edge-Funktion generiert, wenn sie fehlen
description: '',
lore: '',
},
items: [
{
name: formData.artifacts[0]?.name || '',
image_prompt: formData.artifacts[0]?.description || '',
description: '',
lore: '',
},
{
name: formData.artifacts[1]?.name || '',
image_prompt: formData.artifacts[1]?.description || '',
description: '',
lore: '',
},
{
name: formData.artifacts[2]?.name || '',
image_prompt: formData.artifacts[2]?.description || '',
description: '',
lore: '',
},
],
},
};
// Prepare a complete payload with all necessary data
const cleanPayload = {
subject: formData.name, // This is the only required field
rarity: formData.rarity || 'common',
face_image: faceImageBase64,
character_info: {
character: {
image_prompt: formData.characterDescription || '',
description: formData.characterDescription || '',
lore: '',
},
items: formData.artifacts.map((artifact) => ({
name: artifact.name || '',
image_prompt: artifact.description || '',
description: artifact.description || '',
lore: '',
})),
},
};
// Validate payload before sending
if (!cleanPayload.subject) {
throw new Error('Error: Name/Subject is required');
}
// Log payload to see what is being sent
console.log('Sending payload to Edge Function:', JSON.stringify(cleanPayload));
console.log('Payload as string:', JSON.stringify(cleanPayload));
// Call Edge Function with adjusted options for web environments
console.log('Calling Edge Function...');
// Variable for the Edge Function response
let edgeFunctionResponse;
// Use supabase.functions.invoke directly - this handles authentication properly
console.log('Using supabase.functions.invoke...');
console.log('Payload being sent to Edge Function:', JSON.stringify(cleanPayload, null, 2));
let edgeFunctionData = null;
let edgeFunctionError = null;
try {
// Stelle sicher, dass wir einen gültigen Supabase-Client haben
const { data: sessionData } = await supabase.auth.getSession();
if (!sessionData.session) {
console.error('No active session found');
} else {
console.log('Session found, user is authenticated');
}
// Verwende das vollständige Payload mit allen Informationen
console.log('Using complete payload with character info');
// Get the access token for authorization
const accessToken = sessionData?.session?.access_token;
if (!accessToken) {
throw new Error('No access token available. Please log in again.');
}
// Use the known Supabase URL from the error logs
const supabaseUrl = 'https://igxexenivpvivtqkweup.supabase.co';
// Get the anon key using the auth client
const {
data: { session },
} = await supabase.auth.getSession();
const supabaseKey = session?.access_token || '';
// Use direct fetch approach instead of supabase.functions.invoke
const functionUrl = `${supabaseUrl}/functions/v1/barbiebox-generator`;
console.log('Calling Edge Function at URL:', functionUrl);
const response = await fetch(functionUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
apikey: supabaseKey,
},
body: JSON.stringify(cleanPayload),
});
if (!response.ok) {
const errorText = await response.text();
console.error('Edge Function error response:', errorText);
throw new Error(`Edge Function returned status ${response.status}: ${errorText}`);
}
const data = await response.json();
const error = null;
edgeFunctionData = data;
edgeFunctionError = error;
console.log('Edge Function response:', data);
if (error) {
console.error('Edge Function error details:', error);
}
} catch (invokeError) {
console.error('Exception during Edge Function invoke:', invokeError);
throw invokeError;
}
if (edgeFunctionError) {
console.error('Error calling Edge Function:', edgeFunctionError);
throw edgeFunctionError;
}
edgeFunctionResponse = edgeFunctionData;
// Check if the Edge Function response is valid
if (!edgeFunctionResponse || !edgeFunctionResponse.image_url) {
throw new Error('The Edge Function did not return a valid image URL');
}
console.log('Storing figure in database with image URL:', edgeFunctionResponse.image_url);
// Get the user ID
const { data: userData, error: userError } = await supabase.auth.getUser();
if (userError) {
console.error('Error retrieving user:', userError);
throw new Error('User could not be retrieved: ' + userError.message);
}
const userId = userData.user?.id;
if (!userId) {
throw new Error('User ID could not be determined');
}
// Verwende die generierten Beschreibungen von der Edge-Funktion
const generatedDescriptions = edgeFunctionResponse.generated_descriptions;
// Ausführliches Debugging der empfangenen Daten
console.log('FULL Edge Function Response:', JSON.stringify(edgeFunctionResponse, null, 2));
console.log(
'Received generated descriptions from Edge Function:',
JSON.stringify(generatedDescriptions, null, 2)
);
// Prüfe die Struktur der generierten Beschreibungen
if (generatedDescriptions) {
console.log('Generated descriptions structure check:');
console.log('- Has character:', !!generatedDescriptions.character);
if (generatedDescriptions.character) {
console.log(' - Character fields:', Object.keys(generatedDescriptions.character));
console.log(' - Has image_prompt:', !!generatedDescriptions.character.image_prompt);
console.log(' - Has description:', !!generatedDescriptions.character.description);
console.log(' - Has lore:', !!generatedDescriptions.character.lore);
}
console.log('- Has items:', !!generatedDescriptions.items);
if (generatedDescriptions.items && Array.isArray(generatedDescriptions.items)) {
console.log(' - Items count:', generatedDescriptions.items.length);
generatedDescriptions.items.forEach((item: any, index: number) => {
console.log(` - Item ${index + 1} fields:`, Object.keys(item));
console.log(` - Has name:`, !!item.name);
console.log(` - Has image_prompt:`, !!item.image_prompt);
console.log(` - Has description:`, !!item.description);
console.log(` - Has lore:`, !!item.lore);
});
}
}
// Erstelle das character_info JSONB-Objekt basierend auf der neuen Struktur
let characterInfo: any;
// Prüfe, ob die Edge-Funktion die neue JSONB-Struktur zurückgegeben hat
if (generatedDescriptions && generatedDescriptions.character && generatedDescriptions.items) {
// Verwende die vollständige JSONB-Struktur von der Edge-Funktion
console.log('Using new JSONB structure from Edge Function');
// Stelle sicher, dass alle erweiterten Felder vorhanden sind
const character = {
description:
generatedDescriptions.character.description || formData.characterDescription || '',
image_prompt:
generatedDescriptions.character.image_prompt ||
generatedDescriptions.character.description ||
formData.characterDescription ||
'',
lore:
generatedDescriptions.character.lore ||
`${formData.name} has a rich history and background.`,
};
// Stelle sicher, dass alle Items die erweiterten Felder haben
const items = generatedDescriptions.items.map((item: any, index: number) => {
const itemName = item.name || `Item ${index + 1}`;
const itemDesc = item.description || formData.artifacts[index]?.description || '';
return {
name: itemName,
description: itemDesc,
image_prompt: item.image_prompt || itemDesc,
lore: item.lore || `This item has special significance for ${formData.name}.`,
};
});
characterInfo = {
character,
items,
style_description: generatedDescriptions.style_description || '',
};
// Logge die finale Struktur
console.log(
'Final character_info structure to be saved:',
JSON.stringify(characterInfo, null, 2)
);
} else {
// Fallback auf die alte Struktur (sollte nicht mehr vorkommen)
console.log('WARNING: Edge Function returned old format, creating JSONB structure manually');
characterInfo = {
character: {
description:
formData.characterDescription ||
(generatedDescriptions ? generatedDescriptions.clothing_description : ''),
image_prompt:
formData.characterDescription ||
(generatedDescriptions ? generatedDescriptions.clothing_description : ''),
lore: `${formData.name} has a rich history and background.`,
},
items: [
{
name: 'Item 1',
description:
formData.artifacts[0]?.description ||
(generatedDescriptions ? generatedDescriptions.accessory1_description : ''),
image_prompt:
formData.artifacts[0]?.description ||
(generatedDescriptions ? generatedDescriptions.accessory1_description : ''),
lore: `This item has special significance for ${formData.name}.`,
},
{
name: 'Item 2',
description:
formData.artifacts[1]?.description ||
(generatedDescriptions ? generatedDescriptions.accessory2_description : ''),
image_prompt:
formData.artifacts[1]?.description ||
(generatedDescriptions ? generatedDescriptions.accessory2_description : ''),
lore: `This item has special significance for ${formData.name}.`,
},
{
name: 'Item 3',
description:
formData.artifacts[2]?.description ||
(generatedDescriptions ? generatedDescriptions.accessory3_description : ''),
image_prompt:
formData.artifacts[2]?.description ||
(generatedDescriptions ? generatedDescriptions.accessory3_description : ''),
lore: `This item has special significance for ${formData.name}.`,
},
],
};
}
// Store figure in the database
const { data: figureData, error: figureError } = await supabase
.from('figures')
.insert({
name: formData.name,
subject: formData.name,
image_url: edgeFunctionResponse.image_url,
enhanced_prompt: edgeFunctionResponse.enhanced_prompt, // Store the enhanced prompt
rarity: payload.rarity, // Added rarity field
character_info: characterInfo, // Verwende das neue JSONB-Feld
is_public: isPublic,
is_archived: false, // Added is_archived field
user_id: userId,
})
.select()
.single();
if (figureError) {
console.error('Error saving the figure:', figureError);
throw new Error(figureError.message);
}
console.log('Figure successfully saved in the database:', figureData);
return figureData;
} catch (error) {
console.error('Error in generateFigure:', error);
throw error;
}
}
/**
* Loads a user's figures from the database
*/
export async function getUserFigures(userId: string) {
try {
const { data, error } = await supabase
.from('figures')
.select(
`
id,
name,
subject,
image_url,
likes,
is_public,
rarity,
is_archived,
character_info
`
)
.eq('user_id', userId)
.eq('is_archived', false) // Only show non-archived figures
.order('created_at', { ascending: false });
if (error) {
console.error('Error loading figures:', error);
throw new Error(error.message);
}
return data || [];
} catch (error) {
console.error('Error in getUserFigures:', error);
throw error;
}
}
/**
* Loads public figures from the database
*/
export async function getPublicFigures() {
try {
const { data, error } = await supabase
.from('figures')
.select(
`
id,
name,
subject,
image_url,
enhanced_prompt,
likes,
rarity,
user_id,
created_at,
character_info
`
)
.eq('is_public', true)
.eq('is_archived', false) // Only show non-archived figures
.order('created_at', { ascending: false });
if (error) {
console.error('Error loading public figures:', error);
throw new Error(error.message);
}
return data || [];
} catch (error) {
console.error('Error in getPublicFigures:', error);
throw error;
}
}
/**
* Aktualisiert die Likes einer Figur
*/
export async function likeFigure(figureId: number) {
try {
const { data, error } = await supabase.rpc('increment_likes', {
figure_id: figureId,
});
if (error) {
console.error('Fehler beim Liken der Figur:', error);
throw new Error(error.message);
}
return data;
} catch (error) {
console.error('Fehler in likeFigure:', error);
throw error;
}
}