mirror of
https://github.com/mito-systems/ranger-app.git
synced 2026-05-05 13:44:06 +00:00
fixes
This commit is contained in:
parent
dcd7161a64
commit
09b55f9044
@ -252,15 +252,25 @@ export async function POST(req: NextRequest): Promise<NextResponse> {
|
||||
if (pointsUserId && userEmail) {
|
||||
console.log('Awarding points to user:', { pointsUserId, userEmail });
|
||||
try {
|
||||
const pointsResult = await awardPointsForImage(
|
||||
pointsUserId,
|
||||
userEmail,
|
||||
buffer,
|
||||
ipfsUrl,
|
||||
visionResult.description,
|
||||
visionResult.mainObject
|
||||
);
|
||||
console.log('Points award result:', pointsResult);
|
||||
// First ensure the user exists and get the correct user ID
|
||||
// This prevents duplicate users if we're using different IDs between sessions
|
||||
// and ensures we always use the same ID for the same user (by email)
|
||||
const ensuredUserId = await ensureUserExists(pointsUserId, userEmail);
|
||||
console.log('Using ensured user ID for points:', ensuredUserId);
|
||||
|
||||
if (!ensuredUserId) {
|
||||
console.error('Failed to ensure user exists, cannot award points');
|
||||
} else {
|
||||
const pointsResult = await awardPointsForImage(
|
||||
ensuredUserId, // Use the ensured ID, which might be different from pointsUserId
|
||||
userEmail,
|
||||
buffer,
|
||||
ipfsUrl,
|
||||
visionResult.description,
|
||||
visionResult.mainObject
|
||||
);
|
||||
console.log('Points award result:', pointsResult);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to award points for image:', err);
|
||||
console.error('Points error details:', {
|
||||
|
||||
62
src/app/api/debug/merge-users/route.ts
Normal file
62
src/app/api/debug/merge-users/route.ts
Normal file
@ -0,0 +1,62 @@
|
||||
// src/app/api/debug/merge-users/route.ts
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { mergeDuplicateUsersByEmail } from '../../../../services/userPointsService';
|
||||
|
||||
// WARNING: This is a debug endpoint and should be protected in production
|
||||
export async function POST(req: NextRequest): Promise<NextResponse> {
|
||||
try {
|
||||
// For security in production, this should check for admin credentials
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
|
||||
// Require admin authorization in production
|
||||
if (isProduction) {
|
||||
const adminKey = req.headers.get('x-admin-key');
|
||||
const validKey = process.env.ADMIN_DEBUG_KEY;
|
||||
|
||||
if (!adminKey || adminKey !== validKey) {
|
||||
console.warn('Unauthorized access attempt to merge-users API');
|
||||
return NextResponse.json(
|
||||
{ error: 'Unauthorized. Admin access required.' },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse request body
|
||||
const body = await req.json();
|
||||
const { email } = body;
|
||||
|
||||
if (!email) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Email is required' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`Attempting to merge duplicate users for email: ${email}`);
|
||||
|
||||
// Execute the merge
|
||||
const result = await mergeDuplicateUsersByEmail(email);
|
||||
|
||||
console.log('Merge result:', result);
|
||||
|
||||
if (!result.success) {
|
||||
return NextResponse.json(
|
||||
{ error: result.message },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(result);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error in merge-users API:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to merge users' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure dynamic routing
|
||||
export const dynamic = 'force-dynamic';
|
||||
@ -13,6 +13,8 @@ export const POINTS = {
|
||||
|
||||
/**
|
||||
* Ensure a user exists in the database
|
||||
* If a user with the provided email already exists, their ID will be returned
|
||||
* This prevents duplicate users with the same email from being created
|
||||
*/
|
||||
export async function ensureUserExists(userId: string, email: string, name?: string) {
|
||||
try {
|
||||
@ -21,6 +23,11 @@ export async function ensureUserExists(userId: string, email: string, name?: str
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!email) {
|
||||
console.warn('Cannot ensure user exists with undefined email');
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log('Ensuring user exists:', { userId, email });
|
||||
|
||||
// IMPORTANT: Do NOT generate a new UUID, only validate format
|
||||
@ -29,7 +36,7 @@ export async function ensureUserExists(userId: string, email: string, name?: str
|
||||
return null;
|
||||
}
|
||||
|
||||
// Call the Supabase function to ensure user exists
|
||||
// Call the Supabase function to ensure user exists (updated version checks for email first)
|
||||
const { data, error } = await supabaseAdmin.rpc('ensure_user_exists', {
|
||||
user_id_param: userId,
|
||||
email_param: email,
|
||||
@ -39,52 +46,82 @@ export async function ensureUserExists(userId: string, email: string, name?: str
|
||||
if (error) {
|
||||
console.error('Error ensuring user exists:', error);
|
||||
|
||||
// Fallback to direct insert
|
||||
// Fallback to direct query approach
|
||||
try {
|
||||
// First check if the user already exists
|
||||
const { data: existingUser, error: checkError } = await supabaseAdmin
|
||||
// First check if a user with this email already exists
|
||||
const { data: existingUserByEmail, error: emailCheckError } = await supabaseAdmin
|
||||
.from('users')
|
||||
.select('id')
|
||||
.eq('email', email)
|
||||
.maybeSingle();
|
||||
|
||||
if (!emailCheckError && existingUserByEmail) {
|
||||
// User exists by email, update last_login
|
||||
const { error: updateError } = await supabaseAdmin
|
||||
.from('users')
|
||||
.update({
|
||||
last_login: new Date().toISOString(),
|
||||
name: name || undefined // Only update if provided
|
||||
})
|
||||
.eq('id', existingUserByEmail.id);
|
||||
|
||||
if (updateError) {
|
||||
console.warn('Error updating existing user by email:', updateError);
|
||||
}
|
||||
|
||||
console.log('Found existing user by email, using their ID:', existingUserByEmail.id);
|
||||
return existingUserByEmail.id;
|
||||
}
|
||||
|
||||
// If no user found by email, check if one exists with the provided userId
|
||||
const { data: existingUserById, error: idCheckError } = await supabaseAdmin
|
||||
.from('users')
|
||||
.select('id')
|
||||
.eq('id', userId)
|
||||
.maybeSingle();
|
||||
|
||||
if (!checkError && existingUser) {
|
||||
// User exists, update last_login
|
||||
if (!idCheckError && existingUserById) {
|
||||
// User exists by ID, update info
|
||||
const { error: updateError } = await supabaseAdmin
|
||||
.from('users')
|
||||
.update({ last_login: new Date().toISOString() })
|
||||
.update({
|
||||
last_login: new Date().toISOString(),
|
||||
email: email,
|
||||
name: name || undefined // Only update if provided
|
||||
})
|
||||
.eq('id', userId);
|
||||
|
||||
if (updateError) {
|
||||
console.warn('Error updating user last_login:', updateError);
|
||||
console.warn('Error updating existing user by ID:', updateError);
|
||||
}
|
||||
|
||||
return userId;
|
||||
} else {
|
||||
// User doesn't exist, insert new user
|
||||
const { data: insertData, error: insertError } = await supabaseAdmin
|
||||
.from('users')
|
||||
.insert({
|
||||
id: userId,
|
||||
email: email,
|
||||
name: name || null,
|
||||
first_login: new Date().toISOString(),
|
||||
last_login: new Date().toISOString()
|
||||
});
|
||||
|
||||
if (insertError) {
|
||||
console.error('Error inserting user:', insertError);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here, no user exists with this email or ID, so create one
|
||||
const { data: insertData, error: insertError } = await supabaseAdmin
|
||||
.from('users')
|
||||
.insert({
|
||||
id: userId,
|
||||
email: email,
|
||||
name: name || null,
|
||||
first_login: new Date().toISOString(),
|
||||
last_login: new Date().toISOString()
|
||||
});
|
||||
|
||||
return userId;
|
||||
if (insertError) {
|
||||
console.error('Error inserting new user:', insertError);
|
||||
return null;
|
||||
}
|
||||
|
||||
return userId;
|
||||
} catch (err) {
|
||||
console.error('Error in fallback user creation:', err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('User ensured with ID:', data);
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('Unexpected error in ensureUserExists:', error);
|
||||
@ -135,7 +172,8 @@ export async function awardPointsForImage(
|
||||
|
||||
console.log('Award points starting values:', { userId, email });
|
||||
|
||||
// Ensure user exists in database
|
||||
// Ensure user exists in database and get the correct UUID
|
||||
// This ensures we're always using the same UUID for a given email
|
||||
const userUuid = await ensureUserExists(userId, email);
|
||||
if (!userUuid) {
|
||||
console.error('Failed to ensure user exists');
|
||||
@ -144,14 +182,19 @@ export async function awardPointsForImage(
|
||||
message: 'Failed to register user'
|
||||
};
|
||||
}
|
||||
|
||||
// If the ensured UUID is different from the original userId, log it
|
||||
if (userUuid !== userId) {
|
||||
console.log(`Using different UUID for user: ${userUuid} instead of ${userId}`);
|
||||
}
|
||||
|
||||
// Generate hash for the image
|
||||
const imageHash = generateImageHash(imageBuffer);
|
||||
console.log(`Processing points for image ${imageHash.substring(0, 8)}... for user ${userId}`);
|
||||
console.log(`Processing points for image ${imageHash.substring(0, 8)}... for user ${userUuid}`);
|
||||
|
||||
// Check if this image has already been awarded points
|
||||
const { data: existingImage, error: checkError } = await supabaseAdmin.rpc('check_duplicate_upload', {
|
||||
user_id_param: userId,
|
||||
user_id_param: userUuid,
|
||||
image_hash_param: imageHash
|
||||
});
|
||||
|
||||
@ -163,7 +206,7 @@ export async function awardPointsForImage(
|
||||
.from('point_transactions')
|
||||
.select('*')
|
||||
.eq('image_hash', imageHash)
|
||||
.eq('user_id', userId)
|
||||
.eq('user_id', userUuid)
|
||||
.maybeSingle();
|
||||
|
||||
if (!directError && existingTransaction) {
|
||||
@ -184,7 +227,7 @@ export async function awardPointsForImage(
|
||||
// Create transaction using RPC
|
||||
const { data: transactionData, error: rpcError } = await supabaseAdmin
|
||||
.rpc('create_point_transaction', {
|
||||
user_id_param: userId,
|
||||
user_id_param: userUuid,
|
||||
points_param: POINTS.IMAGE_UPLOAD,
|
||||
transaction_type_param: 'image_upload',
|
||||
image_hash_param: imageHash,
|
||||
@ -200,7 +243,7 @@ export async function awardPointsForImage(
|
||||
const { data: insertData, error: insertError } = await supabaseAdmin
|
||||
.from('point_transactions')
|
||||
.insert({
|
||||
user_id: userId,
|
||||
user_id: userUuid,
|
||||
points: POINTS.IMAGE_UPLOAD,
|
||||
transaction_type: 'image_upload',
|
||||
image_hash: imageHash,
|
||||
@ -478,4 +521,59 @@ export async function getUserStats(userId: string) {
|
||||
console.error('Unexpected error in getUserStats:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge duplicate users that have the same email address
|
||||
* This is useful for handling cases where a user has multiple accounts
|
||||
* due to logging in with different authentication methods
|
||||
*/
|
||||
export async function mergeDuplicateUsersByEmail(email: string) {
|
||||
try {
|
||||
if (!email) {
|
||||
console.warn('Cannot merge users with undefined email');
|
||||
return {
|
||||
success: false,
|
||||
message: 'Email is required'
|
||||
};
|
||||
}
|
||||
|
||||
console.log('Attempting to merge users with email:', email);
|
||||
|
||||
// Call the Supabase function to merge users
|
||||
const { data, error } = await supabaseAdmin.rpc('merge_duplicate_users_by_email', {
|
||||
email_param: email
|
||||
});
|
||||
|
||||
if (error) {
|
||||
console.error('Error merging duplicate users:', error);
|
||||
return {
|
||||
success: false,
|
||||
message: `Failed to merge users: ${error.message}`
|
||||
};
|
||||
}
|
||||
|
||||
console.log('Merge result:', data);
|
||||
|
||||
if (!data || data.success === false) {
|
||||
return {
|
||||
success: false,
|
||||
message: data?.message || 'No user found with this email'
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Successfully merged ${data.merged_users_count} duplicate user(s) and migrated ${data.migrated_transactions} transaction(s)`,
|
||||
primaryUserId: data.primary_user_id,
|
||||
mergedCount: data.merged_users_count,
|
||||
email: data.email
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Unexpected error in mergeDuplicateUsersByEmail:', error);
|
||||
return {
|
||||
success: false,
|
||||
message: 'An unexpected error occurred while merging users'
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -165,4 +165,68 @@ BEGIN
|
||||
|
||||
RETURN TRUE;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
||||
|
||||
-- Function to merge duplicate users by email
|
||||
-- This will find all users with the same email, keep the oldest one,
|
||||
-- and migrate all transactions to that user
|
||||
CREATE OR REPLACE FUNCTION merge_duplicate_users_by_email(
|
||||
email_param TEXT
|
||||
)
|
||||
RETURNS json AS $$
|
||||
DECLARE
|
||||
primary_user_id UUID;
|
||||
duplicate_user record;
|
||||
result json;
|
||||
merge_count INT := 0;
|
||||
transaction_count INT := 0;
|
||||
BEGIN
|
||||
-- Find the oldest user with this email to use as the primary
|
||||
SELECT id INTO primary_user_id
|
||||
FROM users
|
||||
WHERE email = email_param
|
||||
ORDER BY first_login ASC
|
||||
LIMIT 1;
|
||||
|
||||
-- If no user found, return error
|
||||
IF primary_user_id IS NULL THEN
|
||||
RETURN json_build_object(
|
||||
'success', FALSE,
|
||||
'message', 'No user found with this email'
|
||||
);
|
||||
END IF;
|
||||
|
||||
-- Find all other users with the same email
|
||||
FOR duplicate_user IN
|
||||
SELECT id
|
||||
FROM users
|
||||
WHERE email = email_param
|
||||
AND id != primary_user_id
|
||||
LOOP
|
||||
-- Migrate this user's transactions to the primary user
|
||||
UPDATE point_transactions
|
||||
SET user_id = primary_user_id
|
||||
WHERE user_id = duplicate_user.id;
|
||||
|
||||
-- Count migrated transactions
|
||||
GET DIAGNOSTICS transaction_count = ROW_COUNT;
|
||||
|
||||
-- Delete the duplicate user
|
||||
DELETE FROM users
|
||||
WHERE id = duplicate_user.id;
|
||||
|
||||
merge_count := merge_count + 1;
|
||||
END LOOP;
|
||||
|
||||
-- Build the result object
|
||||
result := json_build_object(
|
||||
'success', TRUE,
|
||||
'primary_user_id', primary_user_id,
|
||||
'merged_users_count', merge_count,
|
||||
'migrated_transactions', transaction_count,
|
||||
'email', email_param
|
||||
);
|
||||
|
||||
RETURN result;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
||||
@ -37,12 +37,29 @@ CREATE OR REPLACE FUNCTION ensure_user_exists(
|
||||
RETURNS UUID AS $$
|
||||
DECLARE
|
||||
user_uuid UUID;
|
||||
existing_user_id UUID;
|
||||
exists_flag BOOLEAN;
|
||||
BEGIN
|
||||
-- Convert to UUID
|
||||
user_uuid := user_id_param::UUID;
|
||||
|
||||
-- Check if user exists
|
||||
-- First check if a user with this email already exists
|
||||
SELECT id INTO existing_user_id
|
||||
FROM users
|
||||
WHERE email = email_param;
|
||||
|
||||
-- If a user with this email exists, use their ID and update
|
||||
IF FOUND THEN
|
||||
UPDATE users
|
||||
SET last_login = now(),
|
||||
name = COALESCE(name_param, name)
|
||||
WHERE id = existing_user_id;
|
||||
|
||||
RETURN existing_user_id;
|
||||
END IF;
|
||||
|
||||
-- If we get here, no user with this email exists
|
||||
-- Now check if user with this ID exists
|
||||
SELECT EXISTS (
|
||||
SELECT 1 FROM users WHERE id = user_uuid
|
||||
) INTO exists_flag;
|
||||
@ -52,9 +69,11 @@ BEGIN
|
||||
INSERT INTO users (id, email, name, first_login, last_login)
|
||||
VALUES (user_uuid, email_param, name_param, now(), now());
|
||||
ELSE
|
||||
-- Update last_login
|
||||
-- Update last_login and other fields
|
||||
UPDATE users
|
||||
SET last_login = now()
|
||||
SET last_login = now(),
|
||||
email = email_param,
|
||||
name = COALESCE(name_param, name)
|
||||
WHERE id = user_uuid;
|
||||
END IF;
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user