## Database Migrations (18 new) - Migrate all primary keys from SERIAL to UUID - Add soft delete (deleted_at) to all 19 entities - Add missing indexes for performance optimization - Add CHECK constraints for data validation - Add user audit fields (last_login_at, timezone, locale) - Add weather station location fields (latitude, longitude, elevation) - Add foreign key relationships (CameraDevice→Device, ValidatedEvent→WeatherStation) - Prepare private key encryption fields ## Backend Entity Updates - All entities updated with UUID primary keys - Added @DeleteDateColumn for soft delete support - Updated relations and foreign key types ## Backend Service/Controller Updates - Changed ID parameters from number to string (UUID) - Removed ParseIntPipe from controllers - Updated TypeORM queries for string IDs ## Frontend Updates - Updated all service interfaces to use string IDs - Fixed CameraDevice.location as JSONB object - Updated weather.ts with new fields (elevation, timezone) - Added Supabase integration hooks and lib - Fixed chart components for new data structure ## Cleanup - Removed deprecated .claude/agents configuration files 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
168 lines
5.6 KiB
TypeScript
168 lines
5.6 KiB
TypeScript
import { createClient } from '@supabase/supabase-js';
|
|
import { Client, ClientConfig } from 'pg';
|
|
import * as dotenv from 'dotenv';
|
|
|
|
// Load environment variables from .env file
|
|
dotenv.config();
|
|
|
|
const SUPABASE_URL = process.env.SUPABASE_URL || 'https://ffbgowwvcqmdtvvabmnh.supabase.co';
|
|
// Try SUPABASE_SERVICE_ROLE_KEY first, then fall back to SUPABASE_SECRET_KEY
|
|
const SUPABASE_SERVICE_ROLE_KEY = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_SECRET_KEY || '';
|
|
|
|
// Target database configuration (Supabase)
|
|
const targetConfig: ClientConfig = {
|
|
host: 'aws-1-us-east-1.pooler.supabase.com',
|
|
port: 6543,
|
|
database: 'postgres',
|
|
user: 'postgres.ffbgowwvcqmdtvvabmnh',
|
|
password: '!a_KW.-6Grb-X?#',
|
|
ssl: { rejectUnauthorized: false },
|
|
};
|
|
|
|
interface UserIdentity {
|
|
id: string;
|
|
user_profile_id: string;
|
|
provider: string;
|
|
email: string;
|
|
}
|
|
|
|
async function createSupabaseUsers() {
|
|
if (!SUPABASE_SERVICE_ROLE_KEY) {
|
|
console.error('Error: SUPABASE_SERVICE_ROLE_KEY environment variable is required');
|
|
console.log('');
|
|
console.log('To get your service role key:');
|
|
console.log('1. Go to https://supabase.com/dashboard/project/ffbgowwvcqmdtvvabmnh/settings/api');
|
|
console.log('2. Copy the "service_role" key (under "Project API keys")');
|
|
console.log('3. Run: SUPABASE_SERVICE_ROLE_KEY="your-key" npx tsx scripts/create-supabase-users.ts');
|
|
process.exit(1);
|
|
}
|
|
|
|
const supabase = createClient(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, {
|
|
auth: {
|
|
autoRefreshToken: false,
|
|
persistSession: false,
|
|
},
|
|
});
|
|
|
|
const pgClient = new Client(targetConfig);
|
|
|
|
try {
|
|
console.log('Connecting to Supabase database...');
|
|
await pgClient.connect();
|
|
console.log('Connected!');
|
|
|
|
// Get all email users from user_identities
|
|
const result = await pgClient.query<UserIdentity>(`
|
|
SELECT id, user_profile_id, provider, email
|
|
FROM user_identities
|
|
WHERE provider = 'email'
|
|
`);
|
|
|
|
console.log(`\nFound ${result.rows.length} email users to migrate:\n`);
|
|
|
|
const results: { email: string; success: boolean; error?: string; authUserId?: string }[] = [];
|
|
|
|
for (const user of result.rows) {
|
|
console.log(`Processing: ${user.email}`);
|
|
|
|
try {
|
|
// Check if user already exists in Supabase Auth
|
|
const { data: existingUsers } = await supabase.auth.admin.listUsers();
|
|
const existingUser = existingUsers?.users.find((u) => u.email === user.email);
|
|
|
|
let authUserId: string;
|
|
|
|
if (existingUser) {
|
|
console.log(` User already exists in Supabase Auth: ${existingUser.id}`);
|
|
authUserId = existingUser.id;
|
|
} else {
|
|
// Create new user with a temporary password
|
|
// Users will need to reset their password
|
|
const tempPassword = `Temp${Math.random().toString(36).slice(2)}!${Date.now()}`;
|
|
|
|
const { data: newUser, error: createError } = await supabase.auth.admin.createUser({
|
|
email: user.email,
|
|
password: tempPassword,
|
|
email_confirm: true, // Auto-confirm email
|
|
});
|
|
|
|
if (createError) {
|
|
throw new Error(createError.message);
|
|
}
|
|
|
|
if (!newUser?.user) {
|
|
throw new Error('No user returned from createUser');
|
|
}
|
|
|
|
authUserId = newUser.user.id;
|
|
console.log(` Created new Supabase Auth user: ${authUserId}`);
|
|
}
|
|
|
|
// Update user_profiles with supabase_user_id
|
|
await pgClient.query(
|
|
`UPDATE user_profiles SET supabase_user_id = $1 WHERE id = $2`,
|
|
[authUserId, user.user_profile_id]
|
|
);
|
|
console.log(` Updated user_profiles.supabase_user_id`);
|
|
|
|
// Update user_identities with provider_id
|
|
await pgClient.query(
|
|
`UPDATE user_identities SET provider_id = $1 WHERE id = $2`,
|
|
[authUserId, user.id]
|
|
);
|
|
console.log(` Updated user_identities.provider_id`);
|
|
|
|
// Generate password reset link (optional - for sending to users)
|
|
const { data: resetLink, error: resetError } = await supabase.auth.admin.generateLink({
|
|
type: 'recovery',
|
|
email: user.email,
|
|
});
|
|
|
|
if (resetLink && !resetError) {
|
|
console.log(` Password reset link generated`);
|
|
}
|
|
|
|
results.push({ email: user.email, success: true, authUserId });
|
|
console.log(` ✓ Completed\n`);
|
|
|
|
} catch (error: any) {
|
|
console.log(` ✗ Failed: ${error.message}\n`);
|
|
results.push({ email: user.email, success: false, error: error.message });
|
|
}
|
|
}
|
|
|
|
// Print summary
|
|
console.log('\n========================================');
|
|
console.log('Supabase Auth User Creation Summary:');
|
|
console.log('========================================');
|
|
|
|
const successful = results.filter((r) => r.success);
|
|
const failed = results.filter((r) => !r.success);
|
|
|
|
console.log(`\nSuccessful: ${successful.length}`);
|
|
for (const r of successful) {
|
|
console.log(` ✓ ${r.email} -> ${r.authUserId}`);
|
|
}
|
|
|
|
if (failed.length > 0) {
|
|
console.log(`\nFailed: ${failed.length}`);
|
|
for (const r of failed) {
|
|
console.log(` ✗ ${r.email}: ${r.error}`);
|
|
}
|
|
}
|
|
|
|
console.log('\n========================================');
|
|
console.log('\nNote: All users have been created with temporary passwords.');
|
|
console.log('They will need to use "Forgot Password" to set a new password.');
|
|
console.log('========================================\n');
|
|
|
|
} catch (error: any) {
|
|
console.error('Error:', error.message);
|
|
throw error;
|
|
} finally {
|
|
await pgClient.end();
|
|
}
|
|
}
|
|
|
|
createSupabaseUsers().catch(console.error);
|