mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-22 14:51:13 -05:00
Fix Novu migration utility query
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { supabase } from '@/integrations/supabase/client';
|
import { supabase } from '@/integrations/supabase/client';
|
||||||
import { notificationService } from '@/lib/notificationService';
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Progress } from '@/components/ui/progress';
|
import { Progress } from '@/components/ui/progress';
|
||||||
@@ -28,29 +27,31 @@ export function NovuMigrationUtility() {
|
|||||||
setProgress(0);
|
setProgress(0);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// First, fetch user IDs that already have Novu subscriber IDs
|
// Call the server-side migration function
|
||||||
const { data: existingPrefs, error: prefsError } = await supabase
|
const { data: { session } } = await supabase.auth.getSession();
|
||||||
.from('user_notification_preferences')
|
|
||||||
.select('user_id')
|
|
||||||
.not('novu_subscriber_id', 'is', null);
|
|
||||||
|
|
||||||
if (prefsError) throw prefsError;
|
if (!session) {
|
||||||
|
throw new Error('You must be logged in to run the migration');
|
||||||
|
}
|
||||||
|
|
||||||
const existingUserIds = existingPrefs?.map(p => p.user_id) || [];
|
const response = await fetch(
|
||||||
|
'https://ydvtmnrszybqnbcqbdcy.supabase.co/functions/v1/migrate-novu-users',
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${session.access_token}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// Fetch users without Novu subscriber IDs
|
const data = await response.json();
|
||||||
const query = supabase
|
|
||||||
.from('profiles')
|
|
||||||
.select('user_id, users:user_id(email)');
|
|
||||||
|
|
||||||
// Only add the not filter if there are existing user IDs
|
if (!response.ok || !data.success) {
|
||||||
const { data: users, error: fetchError } = existingUserIds.length > 0
|
throw new Error(data.error || 'Migration failed');
|
||||||
? await query.not('user_id', 'in', `(${existingUserIds.join(',')})`)
|
}
|
||||||
: await query;
|
|
||||||
|
|
||||||
if (fetchError) throw fetchError;
|
if (!data.results || data.results.length === 0) {
|
||||||
|
|
||||||
if (!users || users.length === 0) {
|
|
||||||
toast({
|
toast({
|
||||||
title: "No users to migrate",
|
title: "No users to migrate",
|
||||||
description: "All users are already registered with Novu.",
|
description: "All users are already registered with Novu.",
|
||||||
@@ -59,55 +60,12 @@ export function NovuMigrationUtility() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setTotalUsers(users.length);
|
setTotalUsers(data.total);
|
||||||
const migrationResults: MigrationResult[] = [];
|
setResults(data.results);
|
||||||
|
setProgress(100);
|
||||||
|
|
||||||
// Process users one by one
|
const successCount = data.results.filter((r: MigrationResult) => r.success).length;
|
||||||
for (let i = 0; i < users.length; i++) {
|
const failureCount = data.results.filter((r: MigrationResult) => !r.success).length;
|
||||||
const user = users[i];
|
|
||||||
const email = (user.users as any)?.email;
|
|
||||||
|
|
||||||
if (!email) {
|
|
||||||
migrationResults.push({
|
|
||||||
userId: user.user_id,
|
|
||||||
email: 'No email found',
|
|
||||||
success: false,
|
|
||||||
error: 'User email not found',
|
|
||||||
});
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await notificationService.createSubscriber({
|
|
||||||
subscriberId: user.user_id,
|
|
||||||
email,
|
|
||||||
data: { userId: user.user_id },
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationResults.push({
|
|
||||||
userId: user.user_id,
|
|
||||||
email,
|
|
||||||
success: result.success,
|
|
||||||
error: result.error,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Small delay to avoid overwhelming the API
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 100));
|
|
||||||
} catch (error: any) {
|
|
||||||
migrationResults.push({
|
|
||||||
userId: user.user_id,
|
|
||||||
email,
|
|
||||||
success: false,
|
|
||||||
error: error.message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setProgress(((i + 1) / users.length) * 100);
|
|
||||||
setResults([...migrationResults]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const successCount = migrationResults.filter(r => r.success).length;
|
|
||||||
const failureCount = migrationResults.filter(r => !r.success).length;
|
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: "Migration completed",
|
title: "Migration completed",
|
||||||
|
|||||||
153
supabase/functions/migrate-novu-users/index.ts
Normal file
153
supabase/functions/migrate-novu-users/index.ts
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
|
||||||
|
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4";
|
||||||
|
import { Novu } from "npm:@novu/node@2.0.2";
|
||||||
|
|
||||||
|
const corsHeaders = {
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
|
||||||
|
};
|
||||||
|
|
||||||
|
serve(async (req) => {
|
||||||
|
if (req.method === 'OPTIONS') {
|
||||||
|
return new Response(null, { headers: corsHeaders });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const supabaseUrl = Deno.env.get('SUPABASE_URL')!;
|
||||||
|
const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!;
|
||||||
|
const novuApiKey = Deno.env.get('NOVU_API_KEY');
|
||||||
|
|
||||||
|
if (!novuApiKey) {
|
||||||
|
throw new Error('NOVU_API_KEY is not configured');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Supabase client with service role for admin access
|
||||||
|
const supabase = createClient(supabaseUrl, supabaseServiceKey);
|
||||||
|
|
||||||
|
const novu = new Novu(novuApiKey, {
|
||||||
|
backendUrl: Deno.env.get('VITE_NOVU_API_URL') || 'https://api.novu.co',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetch users who don't have Novu subscriber IDs
|
||||||
|
const { data: existingPrefs, error: prefsError } = await supabase
|
||||||
|
.from('user_notification_preferences')
|
||||||
|
.select('user_id')
|
||||||
|
.not('novu_subscriber_id', 'is', null);
|
||||||
|
|
||||||
|
if (prefsError) throw prefsError;
|
||||||
|
|
||||||
|
const existingUserIds = existingPrefs?.map(p => p.user_id) || [];
|
||||||
|
|
||||||
|
// Fetch all profiles
|
||||||
|
let query = supabase
|
||||||
|
.from('profiles')
|
||||||
|
.select('user_id');
|
||||||
|
|
||||||
|
// Only add the not filter if there are existing user IDs
|
||||||
|
if (existingUserIds.length > 0) {
|
||||||
|
query = query.not('user_id', 'in', `(${existingUserIds.join(',')})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: profiles, error: profilesError } = await query;
|
||||||
|
|
||||||
|
if (profilesError) throw profilesError;
|
||||||
|
|
||||||
|
if (!profiles || profiles.length === 0) {
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'No users to migrate',
|
||||||
|
results: [],
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
||||||
|
status: 200,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch user emails from auth.users using service role
|
||||||
|
const userIds = profiles.map(p => p.user_id);
|
||||||
|
const { data: authUsers, error: authError } = await supabase.auth.admin.listUsers();
|
||||||
|
|
||||||
|
if (authError) throw authError;
|
||||||
|
|
||||||
|
const userEmails = new Map(
|
||||||
|
authUsers.users
|
||||||
|
.filter(u => userIds.includes(u.id))
|
||||||
|
.map(u => [u.id, u.email])
|
||||||
|
);
|
||||||
|
|
||||||
|
// Migrate users
|
||||||
|
const results = [];
|
||||||
|
for (const profile of profiles) {
|
||||||
|
const email = userEmails.get(profile.user_id);
|
||||||
|
|
||||||
|
if (!email) {
|
||||||
|
results.push({
|
||||||
|
userId: profile.user_id,
|
||||||
|
email: 'No email found',
|
||||||
|
success: false,
|
||||||
|
error: 'User email not found',
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const subscriber = await novu.subscribers.identify(profile.user_id, {
|
||||||
|
email,
|
||||||
|
data: { userId: profile.user_id },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update the user's notification preferences with the Novu subscriber ID
|
||||||
|
await supabase
|
||||||
|
.from('user_notification_preferences')
|
||||||
|
.upsert({
|
||||||
|
user_id: profile.user_id,
|
||||||
|
novu_subscriber_id: subscriber.data._id,
|
||||||
|
});
|
||||||
|
|
||||||
|
results.push({
|
||||||
|
userId: profile.user_id,
|
||||||
|
email,
|
||||||
|
success: true,
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
results.push({
|
||||||
|
userId: profile.user_id,
|
||||||
|
email,
|
||||||
|
success: false,
|
||||||
|
error: error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Small delay to avoid overwhelming the API
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
total: profiles.length,
|
||||||
|
results,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
||||||
|
status: 200,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Error migrating Novu users:', error);
|
||||||
|
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
success: false,
|
||||||
|
error: error.message,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
||||||
|
status: 500,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user