From 7239fd45f4e907ed0294313e9d5055def633fd58 Mon Sep 17 00:00:00 2001 From: zramsay Date: Tue, 11 Mar 2025 18:38:05 -0400 Subject: [PATCH] points --- src/services/userPointsService.ts | 243 ++++++++++++++++++++---------- 1 file changed, 167 insertions(+), 76 deletions(-) diff --git a/src/services/userPointsService.ts b/src/services/userPointsService.ts index 1f9f7a4..66350c0 100644 --- a/src/services/userPointsService.ts +++ b/src/services/userPointsService.ts @@ -134,22 +134,23 @@ export async function awardPointsForImage( }; } - // If userId is an email, use it for consistent storage - if (userId.includes('@')) { - console.log('userId is an email, using it as the email field'); - // But keep the userId as is for consistent UUID generation + // Log what email and userId we're starting with + console.log('Award points starting values:', { userId, email }); + + // Make sure we have a valid email - NOT a generated one + if (email === 'unknown@example.com' || email.endsWith('@user.wildlife.app')) { + console.log('WARNING: Using a synthetic email address:', email); } - // If email is missing but userId looks like an email, use that - if (!email && userId.includes('@')) { - email = userId; - console.log('Using userId as email since it contains @:', email); - } - - // Ensure we have a valid email + // Always prefer the email parameter directly from the authentication system if (!email || email === 'unknown@example.com') { - email = userId.includes('@') ? userId : `${userId}@user.wildlife.app`; - console.log('Generated proper email for points system:', email); + if (userId.includes('@')) { + email = userId; + console.log('Using userId as email since it looks like an email:', email); + } else { + // This should be very rare - only if both auth email and userId are invalid + console.log('WARNING: Both userId and email are invalid or missing'); + } } // Convert userId to UUID format @@ -201,38 +202,87 @@ export async function awardPointsForImage( console.log('Creating point transaction for user:', userIdUUID); - // Try to create transaction using RPC first with admin client - const { data: transactionData, error: rpcError } = await supabaseAdmin - .rpc('create_point_transaction', { - user_id_param: userIdUUID, - points_param: POINTS.IMAGE_UPLOAD, - transaction_type_param: 'image_upload', - image_hash_param: imageHash, - image_url_param: imageUrl, - description_param: description, - animal_type_param: animalType - }); - - if (rpcError) { - console.log('Info: Could not create transaction via RPC, trying direct insert:', rpcError.message); - - // Fallback to direct insert with admin client - console.log('Attempting direct insert for transaction...'); - const { error: transactionError } = await supabaseAdmin - .from('point_transactions') - .insert({ - user_id: userIdUUID, - points: POINTS.IMAGE_UPLOAD, - transaction_type: 'image_upload', - image_hash: imageHash, - image_url: imageUrl, - description, - animal_type: animalType, - created_at: new Date().toISOString() + // We'll try these approaches in sequence, with detailed logging + let transactionCreated = false; + + // 1. Try to create transaction using RPC first with admin client + try { + console.log('Attempting to create transaction via RPC...'); + const { data: transactionData, error: rpcError } = await supabaseAdmin + .rpc('create_point_transaction', { + user_id_param: userIdUUID, + points_param: POINTS.IMAGE_UPLOAD, + transaction_type_param: 'image_upload', + image_hash_param: imageHash, + image_url_param: imageUrl, + description_param: description, + animal_type_param: animalType }); - - if (transactionError) { - console.warn('Direct transaction insert also failed:', transactionError.message); + + if (!rpcError) { + console.log('Successfully created transaction via RPC:', transactionData); + transactionCreated = true; + } else { + console.log('Could not create transaction via RPC:', rpcError.message); + } + } catch (error) { + console.error('Exception in RPC transaction creation:', error); + } + + // 2. Fallback to direct insert with admin client + if (!transactionCreated) { + try { + console.log('Attempting direct insert for transaction...'); + const { data: insertData, error: transactionError } = await supabaseAdmin + .from('point_transactions') + .insert({ + user_id: userIdUUID, + points: POINTS.IMAGE_UPLOAD, + transaction_type: 'image_upload', + image_hash: imageHash, + image_url: imageUrl, + description, + animal_type: animalType + }) + .select() + .single(); + + if (transactionError) { + console.warn('Direct transaction insert failed:', transactionError.message); + } else { + console.log('Successfully created transaction via direct insert:', insertData); + transactionCreated = true; + } + } catch (error) { + console.error('Exception in direct transaction insert:', error); + } + } + + // 3. Last resort insert with minimal fields + if (!transactionCreated) { + try { + console.log('Attempting minimal transaction insert as last resort...'); + const { error: minimalError } = await supabaseAdmin + .from('point_transactions') + .insert({ + user_id: userIdUUID, + points: POINTS.IMAGE_UPLOAD, + transaction_type: 'image_upload', + image_hash: imageHash + }); + + if (minimalError) { + console.warn('Even minimal transaction insert failed:', minimalError.message); + return { + success: false, + message: 'Failed to record points transaction after multiple attempts' + }; + } else { + console.log('Successfully created minimal transaction'); + transactionCreated = true; + } + } catch (error) { + console.error('Exception in minimal transaction insert:', error); return { success: false, message: 'Failed to record points transaction' @@ -245,44 +295,89 @@ export async function awardPointsForImage( let pointsUpdated = false; - // Try RPC method first with admin client - const { error: updateError } = await supabaseAdmin.rpc('increment_user_points', { - user_id_param: userIdUUID, - points_param: POINTS.IMAGE_UPLOAD - }); - - if (!updateError) { - pointsUpdated = true; - } else { - console.log('Info: Could not update points via RPC, trying direct update:', updateError.message); - - // Fallback: Direct update with admin client + // 1. Try RPC method first with admin client + try { + console.log('Attempting to update points via RPC...'); + const { data: rpcData, error: updateError } = await supabaseAdmin.rpc('increment_user_points', { + user_id_param: userIdUUID, + points_param: POINTS.IMAGE_UPLOAD + }); + + if (!updateError) { + console.log('Successfully updated points via RPC:', rpcData); + pointsUpdated = true; + } else { + console.log('Could not update points via RPC:', updateError.message); + } + } catch (error) { + console.error('Exception in RPC points update:', error); + } + + // 2. Fallback: Direct update with admin client + if (!pointsUpdated) { try { + console.log('Attempting direct update for points...'); + // Get current points first with admin client - const { data: currentPointsData } = await supabaseAdmin + const { data: currentPointsData, error: selectError } = await supabaseAdmin .from('user_points') .select('total_points') .eq('user_id', userIdUUID) .maybeSingle(); - - const currentPoints = currentPointsData?.total_points || 0; - // Then update with new total with admin client - const { error: fallbackError } = await supabaseAdmin + if (selectError) { + console.warn('Could not get current points:', selectError.message); + } else { + console.log('Current points data:', currentPointsData); + + const currentPoints = currentPointsData?.total_points || 0; + const newPoints = currentPoints + POINTS.IMAGE_UPLOAD; + + console.log(`Updating points from ${currentPoints} to ${newPoints}`); + + // Then update with new total with admin client + const { data: updateData, error: fallbackError } = await supabaseAdmin + .from('user_points') + .update({ + total_points: newPoints, + updated_at: new Date().toISOString() + }) + .eq('user_id', userIdUUID) + .select() + .single(); + + if (!fallbackError) { + console.log('Successfully updated points via direct update:', updateData); + pointsUpdated = true; + } else { + console.warn('Direct points update failed:', fallbackError.message); + } + } + } catch (err) { + console.error('Exception in direct points update:', err); + } + } + + // 3. Last resort: Force an update to 1 point if this is their first transaction + if (!pointsUpdated) { + try { + console.log('Attempting force update as last resort...'); + const { error: forceError } = await supabaseAdmin .from('user_points') .update({ - total_points: currentPoints + POINTS.IMAGE_UPLOAD, + total_points: 1, // Force to at least 1 point updated_at: new Date().toISOString() }) .eq('user_id', userIdUUID); - if (!fallbackError) { + if (!forceError) { + console.log('Successfully forced points update to 1'); pointsUpdated = true; } else { - console.warn('Direct points update also failed:', fallbackError.message); + console.warn('Even forced points update failed:', forceError.message); } } catch (err) { - console.warn('Error in points update fallback:', err); + console.error('Exception in forced points update:', err); } } @@ -345,19 +440,15 @@ export async function getUserPoints(userId: string) { .eq('user_id', userIdUUID) .maybeSingle(); - // If we got an empty result or a not found error, try to initialize the user + // If we got an empty result or a not found error, the user doesn't exist yet if (!userExists || (checkError && checkError.code === 'PGRST116')) { - // Check if the userId is an email - let email; - if (userId.includes('@')) { - email = userId; // Use the userId as email if it is one - console.log('Using email from userId for initialization:', email); - } else { - // Use a real email format if possible - but don't use example.com - email = userId.includes('@') ? userId : `${userId}@user.wildlife.app`; - console.log('Using generated email for initialization:', email); - } + console.log('User does not exist yet in user_points table, need to initialize'); + // At this point, the user should already be authenticated, so we should have their real email + // But if we don't, we need to handle that case + const email = userId.includes('@') ? userId : `${userId}@user.wildlife.app`; + + console.log('Initializing user with:', { userId, email }); await initializeUser(userId, email); } } catch (initError) {