Create scripts/migrate-users.ts that: - Creates a default admin user from DEFAULT_ADMIN_EMAIL/DEFAULT_ADMIN_PASSWORD env vars - Backfills user_id on all existing rows in charts, annotations, annotation_types, span_annotations, span_label_types - Is idempotent (safe to run multiple times) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
89 lines
2.6 KiB
TypeScript
89 lines
2.6 KiB
TypeScript
/**
|
|
* Data migration script for user-accounts feature (Task 2.5)
|
|
*
|
|
* 1. Creates a default admin user from DEFAULT_ADMIN_EMAIL / DEFAULT_ADMIN_PASSWORD env vars
|
|
* 2. Backfills user_id on all existing rows in charts, annotations, annotation_types,
|
|
* span_annotations, span_label_types to the default admin user's ID
|
|
*
|
|
* Idempotent: safe to run multiple times.
|
|
*
|
|
* Usage: DATABASE_URL=... DEFAULT_ADMIN_EMAIL=... DEFAULT_ADMIN_PASSWORD=... npx tsx scripts/migrate-users.ts
|
|
*/
|
|
|
|
import { Pool } from 'pg';
|
|
import bcrypt from 'bcryptjs';
|
|
|
|
const SALT_ROUNDS = 12;
|
|
|
|
async function main() {
|
|
const DATABASE_URL = process.env.DATABASE_URL;
|
|
if (!DATABASE_URL) {
|
|
throw new Error('DATABASE_URL environment variable is required');
|
|
}
|
|
|
|
const adminEmail = process.env.DEFAULT_ADMIN_EMAIL;
|
|
const adminPassword = process.env.DEFAULT_ADMIN_PASSWORD;
|
|
if (!adminEmail || !adminPassword) {
|
|
throw new Error(
|
|
'DEFAULT_ADMIN_EMAIL and DEFAULT_ADMIN_PASSWORD environment variables are required'
|
|
);
|
|
}
|
|
|
|
const pool = new Pool({ connectionString: DATABASE_URL });
|
|
|
|
try {
|
|
// ---- Step 1: Upsert default admin user ----
|
|
console.log(`Creating/finding default admin user: ${adminEmail}`);
|
|
|
|
// Check if user already exists
|
|
const existing = await pool.query(
|
|
'SELECT id FROM users WHERE email = $1',
|
|
[adminEmail]
|
|
);
|
|
|
|
let adminUserId: string;
|
|
|
|
if (existing.rows.length > 0) {
|
|
adminUserId = existing.rows[0].id;
|
|
console.log(` Admin user already exists (id: ${adminUserId})`);
|
|
} else {
|
|
const passwordHash = await bcrypt.hash(adminPassword, SALT_ROUNDS);
|
|
const inserted = await pool.query(
|
|
`INSERT INTO users (email, password_hash, name, provider)
|
|
VALUES ($1, $2, $3, 'credentials')
|
|
RETURNING id`,
|
|
[adminEmail, passwordHash, 'Admin']
|
|
);
|
|
adminUserId = inserted.rows[0].id;
|
|
console.log(` Admin user created (id: ${adminUserId})`);
|
|
}
|
|
|
|
// ---- Step 2: Backfill user_id on all existing rows ----
|
|
const tables = [
|
|
'charts',
|
|
'annotations',
|
|
'annotation_types',
|
|
'span_annotations',
|
|
'span_label_types',
|
|
];
|
|
|
|
for (const table of tables) {
|
|
const result = await pool.query(
|
|
`UPDATE ${table} SET user_id = $1 WHERE user_id IS NULL`,
|
|
[adminUserId]
|
|
);
|
|
console.log(` ${table}: backfilled ${result.rowCount} rows`);
|
|
}
|
|
|
|
console.log('Migration complete.');
|
|
} finally {
|
|
await pool.end();
|
|
}
|
|
}
|
|
|
|
main()
|
|
.then(() => process.exit(0))
|
|
.catch((err) => {
|
|
console.error('Migration failed:', err);
|
|
process.exit(1);
|
|
});
|