mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-22 13:31:22 -05:00
Fix Novu migration utility query
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import { notificationService } from '@/lib/notificationService';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
@@ -28,29 +27,31 @@ export function NovuMigrationUtility() {
|
||||
setProgress(0);
|
||||
|
||||
try {
|
||||
// First, fetch user IDs that already have Novu subscriber IDs
|
||||
const { data: existingPrefs, error: prefsError } = await supabase
|
||||
.from('user_notification_preferences')
|
||||
.select('user_id')
|
||||
.not('novu_subscriber_id', 'is', null);
|
||||
// Call the server-side migration function
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
|
||||
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 query = supabase
|
||||
.from('profiles')
|
||||
.select('user_id, users:user_id(email)');
|
||||
const data = await response.json();
|
||||
|
||||
// Only add the not filter if there are existing user IDs
|
||||
const { data: users, error: fetchError } = existingUserIds.length > 0
|
||||
? await query.not('user_id', 'in', `(${existingUserIds.join(',')})`)
|
||||
: await query;
|
||||
if (!response.ok || !data.success) {
|
||||
throw new Error(data.error || 'Migration failed');
|
||||
}
|
||||
|
||||
if (fetchError) throw fetchError;
|
||||
|
||||
if (!users || users.length === 0) {
|
||||
if (!data.results || data.results.length === 0) {
|
||||
toast({
|
||||
title: "No users to migrate",
|
||||
description: "All users are already registered with Novu.",
|
||||
@@ -59,55 +60,12 @@ export function NovuMigrationUtility() {
|
||||
return;
|
||||
}
|
||||
|
||||
setTotalUsers(users.length);
|
||||
const migrationResults: MigrationResult[] = [];
|
||||
setTotalUsers(data.total);
|
||||
setResults(data.results);
|
||||
setProgress(100);
|
||||
|
||||
// Process users one by one
|
||||
for (let i = 0; i < users.length; i++) {
|
||||
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;
|
||||
const successCount = data.results.filter((r: MigrationResult) => r.success).length;
|
||||
const failureCount = data.results.filter((r: MigrationResult) => !r.success).length;
|
||||
|
||||
toast({
|
||||
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