mirror of
https://github.com/AeThex-Corporation/AeThex-OS.git
synced 2026-04-18 14:37:19 +00:00
- Add revenue_events table to track org/project revenue with source tracking - Add Drizzle schema for revenue_events with proper org/project references - Create migration 0006_revenue_events.sql with indexes - Fix migration 0004: Remove FK constraints to profiles.id (auth schema incompatibility) - Document auth.users/profiles.id type mismatch (UUID vs VARCHAR) - Harden profile update authorization (self-update or org admin/owner only) - Complete org-scoping security audit implementation (42 gaps closed)
152 lines
4.4 KiB
TypeScript
152 lines
4.4 KiB
TypeScript
import dotenv from "dotenv";
|
|
dotenv.config({ path: './.env' });
|
|
|
|
import { supabase } from "../server/supabase.js";
|
|
|
|
/**
|
|
* Backfill Script: Create default organizations for existing users
|
|
*
|
|
* This script:
|
|
* 1. Creates a default organization for each existing user profile
|
|
* 2. Adds the user as organization owner
|
|
* 3. Backfills organization_id for user-owned entities
|
|
*/
|
|
|
|
async function generateSlug(name: string): Promise<string> {
|
|
const baseSlug = name
|
|
.toLowerCase()
|
|
.replace(/[^a-z0-9]+/g, '-')
|
|
.replace(/^-|-$/g, '');
|
|
|
|
// Check if slug exists
|
|
const { data: existing } = await supabase
|
|
.from('organizations')
|
|
.select('slug')
|
|
.eq('slug', baseSlug)
|
|
.single();
|
|
|
|
if (!existing) return baseSlug;
|
|
|
|
// Add random suffix if collision
|
|
const suffix = Math.random().toString(36).substring(2, 6);
|
|
return `${baseSlug}-${suffix}`;
|
|
}
|
|
|
|
async function backfillOrganizations() {
|
|
console.log('Starting organization backfill...\n');
|
|
|
|
try {
|
|
// Get all user profiles
|
|
const { data: profiles, error: profilesError } = await supabase
|
|
.from('profiles')
|
|
.select('id, username, full_name, email');
|
|
|
|
if (profilesError) {
|
|
throw new Error(`Failed to fetch profiles: ${profilesError.message}`);
|
|
}
|
|
|
|
console.log(`Found ${profiles?.length || 0} user profiles\n`);
|
|
|
|
for (const profile of profiles || []) {
|
|
const displayName = profile.full_name || profile.username || profile.email?.split('@')[0] || 'User';
|
|
const orgName = `${displayName}'s Workspace`;
|
|
const slug = await generateSlug(displayName);
|
|
|
|
console.log(`Creating organization for user ${profile.id} (${displayName})...`);
|
|
|
|
// Check if org already exists for this user
|
|
const { data: existingOrg } = await supabase
|
|
.from('organizations')
|
|
.select('id')
|
|
.eq('owner_user_id', profile.id)
|
|
.single();
|
|
|
|
let orgId: string;
|
|
|
|
if (existingOrg) {
|
|
console.log(` ✓ Organization already exists: ${existingOrg.id}`);
|
|
orgId = existingOrg.id;
|
|
} else {
|
|
// Create organization
|
|
const { data: newOrg, error: orgError } = await supabase
|
|
.from('organizations')
|
|
.insert({
|
|
name: orgName,
|
|
slug: slug,
|
|
owner_user_id: profile.id,
|
|
plan: 'free',
|
|
})
|
|
.select()
|
|
.single();
|
|
|
|
if (orgError) {
|
|
console.error(` ✗ Failed to create org: ${orgError.message}`);
|
|
continue;
|
|
}
|
|
|
|
orgId = newOrg!.id;
|
|
console.log(` ✓ Created organization: ${orgId} (${orgName})`);
|
|
|
|
// Add user as organization member with 'owner' role
|
|
const { error: memberError } = await supabase
|
|
.from('organization_members')
|
|
.insert({
|
|
organization_id: orgId,
|
|
user_id: profile.id,
|
|
role: 'owner',
|
|
});
|
|
|
|
if (memberError) {
|
|
console.error(` ✗ Failed to add member: ${memberError.message}`);
|
|
continue;
|
|
}
|
|
|
|
console.log(` ✓ Added user as owner`);
|
|
}
|
|
|
|
// Backfill organization_id for user's entities
|
|
await backfillUserEntities(profile.id, orgId);
|
|
|
|
console.log('');
|
|
}
|
|
|
|
console.log('\n✅ Backfill complete!');
|
|
} catch (error) {
|
|
console.error('❌ Backfill failed:', error);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
async function backfillUserEntities(userId: string, orgId: string) {
|
|
const tables = [
|
|
{ name: 'projects', ownerField: 'owner_user_id' },
|
|
{ name: 'aethex_projects', ownerField: 'creator_id' },
|
|
{ name: 'marketplace_listings', ownerField: 'seller_id' },
|
|
{ name: 'files', ownerField: 'user_id' },
|
|
{ name: 'custom_apps', ownerField: 'creator_id' },
|
|
{ name: 'aethex_sites', ownerField: 'owner_id' },
|
|
];
|
|
|
|
for (const table of tables) {
|
|
try {
|
|
const { data, error } = await supabase
|
|
.from(table.name)
|
|
.update({ organization_id: orgId })
|
|
.eq(table.ownerField, userId)
|
|
.is('organization_id', null)
|
|
.select('id');
|
|
|
|
if (error) {
|
|
console.error(` ✗ Failed to backfill ${table.name}: ${error.message}`);
|
|
} else if (data && data.length > 0) {
|
|
console.log(` ✓ Backfilled ${data.length} ${table.name} records`);
|
|
}
|
|
} catch (err) {
|
|
console.error(` ✗ Error backfilling ${table.name}:`, err);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Run the script
|
|
backfillOrganizations();
|
|
|