grabbit f557c06771 feat: complete database schema migration to UUID primary keys
## 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>
2025-12-21 03:33:26 +08:00

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);