diff --git a/src/app/api/analyze/route.ts b/src/app/api/analyze/route.ts index 83a6469..3af695d 100644 --- a/src/app/api/analyze/route.ts +++ b/src/app/api/analyze/route.ts @@ -5,7 +5,8 @@ import { analyzeImageWithVision } from '../../../services/googleVisionCore' import { processAnimalImage } from '../../../services/animalProcessingService' import { checkAndStoreImageHash } from '../../../services/imageHashService' import { awardPointsForImage, ensureUserExists } from '../../../services/userPointsService' -import { validate as validateUuid } from 'uuid' +import { validate as validateUuid, v4 as uuidv4 } from 'uuid' +import { supabaseAdmin } from '../../../services/supabaseClient' // Increase body parser size limit export const config = { @@ -145,6 +146,15 @@ export async function POST(req: NextRequest): Promise { const formData = await req.formData() const imageFile = formData.get('image') + // Check for wallet address in form data + const walletAddress = formData.get('walletAddress'); + console.log('Form data received:', { + hasImage: !!imageFile, + imageType: imageFile ? typeof imageFile : 'undefined', + walletAddress: walletAddress || 'Not provided', + formDataKeys: Array.from(formData.keys()) + }); + // For server-side, check if we have a valid Blob-like object // In Node.js environment, Formidable gives us a Blob-like interface if (!imageFile || !(typeof imageFile === 'object' && 'arrayBuffer' in (imageFile as object))) { @@ -257,6 +267,14 @@ export async function POST(req: NextRequest): Promise { if (pointsUserId && userEmail) { console.log('Awarding points to user:', { pointsUserId, userEmail }); + + // Log validation details + console.log('User ID validation:', { + userId: pointsUserId, + isValidUuid: validateUuid(pointsUserId), + userEmail + }); + try { // First ensure the user exists and get the correct user ID // This prevents duplicate users if we're using different IDs between sessions @@ -264,12 +282,71 @@ export async function POST(req: NextRequest): Promise { const ensuredUserId = await ensureUserExists(pointsUserId, userEmail); console.log('Using ensured user ID for points:', ensuredUserId); + // If we couldn't ensure user exists, try creating directly if (!ensuredUserId) { - console.error('Failed to ensure user exists, cannot award points'); + console.error('Failed to ensure user exists - trying direct creation'); - // Even if we can't award points, we should still return a successful response - // so the user gets some useful feedback about their image - return NextResponse.json(responseData); + // Generate a new valid UUID if needed + const userId = validateUuid(pointsUserId) ? pointsUserId : uuidv4(); + + try { + // Check if a user with this email already exists + const { data: existingUser } = await supabaseAdmin + .from('users') + .select('id') + .eq('email', userEmail) + .maybeSingle(); + + if (existingUser) { + console.log('Found existing user by email:', existingUser.id); + + // Award points using the existing user ID + const pointsResult = await awardPointsForImage( + existingUser.id, + userEmail, + buffer, + ipfsUrl, + visionResult.description, + visionResult.mainObject + ); + + console.log('Points result for existing user:', pointsResult); + } else { + // Create a new user directly + const { data: newUser, error: insertError } = await supabaseAdmin + .from('users') + .insert({ + id: userId, + email: userEmail, + first_login: new Date().toISOString(), + last_login: new Date().toISOString() + }) + .select() + .single(); + + if (insertError) { + console.error('Error creating user directly:', insertError); + } else { + console.log('Created new user directly:', newUser); + + // Award points using the new user ID + const pointsResult = await awardPointsForImage( + newUser.id, + userEmail, + buffer, + ipfsUrl, + visionResult.description, + visionResult.mainObject + ); + + console.log('Points result for new user:', pointsResult); + } + } + } catch (directError) { + console.error('Error in direct user handling:', directError); + } + + // Continue with token rewards even if points fail - don't return yet } else { // Award points first - this should always happen regardless of wallet status console.log('Attempting to award points for user:', { ensuredUserId, userEmail }); @@ -300,6 +377,9 @@ export async function POST(req: NextRequest): Promise { // Check if wallet address was provided in the form data const walletAddress = formData.get('walletAddress') as string; + // Log the wallet address for debugging + console.log('Wallet address for token award:', walletAddress || 'Not provided'); + // Import dynamically to avoid server-side issues with window object const { awardTokensForSighting } = await import('../../../services/blockchain/tokenRewardService'); diff --git a/src/services/blockchain/seiService.ts b/src/services/blockchain/seiService.ts index d504fb5..535a9bd 100644 --- a/src/services/blockchain/seiService.ts +++ b/src/services/blockchain/seiService.ts @@ -33,6 +33,21 @@ let currentWallet: OfflineSigner | null = null; let currentAddress: string | null = null; let cosmWasmClient: any = null; +// Initialize from localStorage if available (client-side only) +if (typeof window !== 'undefined') { + try { + const savedAddress = localStorage.getItem('wildlife_wallet_address'); + if (savedAddress) { + console.log('Restoring wallet address from localStorage:', savedAddress); + currentAddress = savedAddress; + // Note: We can't restore the actual wallet object, but we can restore the address + // The wallet object will be reconnected when needed + } + } catch (e) { + console.warn('Failed to restore wallet from localStorage:', e); + } +} + // Custom event for wallet status changes export const WALLET_EVENTS = { CONNECTED: 'wallet_connected', @@ -132,6 +147,16 @@ export const connectSeiWallet = async (): Promise => { currentWallet = offlineSigner as any; currentAddress = address; + // Save address to localStorage for persistence + if (typeof window !== 'undefined') { + try { + localStorage.setItem('wildlife_wallet_address', address); + console.log('Wallet address saved to localStorage:', address); + } catch (e) { + console.warn('Failed to save wallet address to localStorage:', e); + } + } + // Initialize CosmWasm client for contract interactions cosmWasmClient = await SigningCosmWasmClient.connectWithSigner( network.rpcUrl, @@ -156,6 +181,17 @@ export const disconnectSeiWallet = () => { currentWallet = null; currentAddress = null; cosmWasmClient = null; + + // Clear from localStorage + if (typeof window !== 'undefined') { + try { + localStorage.removeItem('wildlife_wallet_address'); + console.log('Wallet address removed from localStorage'); + } catch (e) { + console.warn('Failed to remove wallet address from localStorage:', e); + } + } + dispatchWalletEvent(WALLET_EVENTS.DISCONNECTED); console.log('Sei wallet disconnected'); return true; @@ -163,9 +199,11 @@ export const disconnectSeiWallet = () => { /** * Check if wallet is connected + * If we have an address but no wallet, we consider it connected + * since the address was previously saved */ export const isWalletConnected = (): boolean => { - return currentWallet !== null && currentAddress !== null; + return currentAddress !== null; }; /**