Add data migration script for user-accounts (task 2.5)
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>
This commit is contained in:
parent
877ae032a1
commit
73f44bf447
2 changed files with 90 additions and 1 deletions
|
|
@ -10,7 +10,7 @@
|
||||||
- [x] 2.2 `[sonnet]` Add `user_id` (uuid, FK to users.id) column to `charts`, `annotations`, `annotation_types`, `span_annotations`, `span_label_types` in schema
|
- [x] 2.2 `[sonnet]` Add `user_id` (uuid, FK to users.id) column to `charts`, `annotations`, `annotation_types`, `span_annotations`, `span_label_types` in schema
|
||||||
- [x] 2.3 `[sonnet]` Replace unique constraints: `charts.name` → `(user_id, name)`, `annotation_types.name` → `(user_id, name)`, `span_label_types.name` → `(user_id, name)`
|
- [x] 2.3 `[sonnet]` Replace unique constraints: `charts.name` → `(user_id, name)`, `annotation_types.name` → `(user_id, name)`, `span_label_types.name` → `(user_id, name)`
|
||||||
- [x] 2.4 `[haiku]` Generate Drizzle migration with `drizzle-kit generate`
|
- [x] 2.4 `[haiku]` Generate Drizzle migration with `drizzle-kit generate`
|
||||||
- [ ] 2.5 `[opus]` Create data migration script (`scripts/migrate-users.ts`): create default admin user, backfill `user_id` on all existing rows, alter columns to NOT NULL
|
- [x] 2.5 `[opus]` Create data migration script (`scripts/migrate-users.ts`): create default admin user, backfill `user_id` on all existing rows, alter columns to NOT NULL
|
||||||
|
|
||||||
## 3. Auth.js Configuration
|
## 3. Auth.js Configuration
|
||||||
|
|
||||||
|
|
|
||||||
89
scripts/migrate-users.ts
Normal file
89
scripts/migrate-users.ts
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue