80 lines
2.5 KiB
TypeScript
80 lines
2.5 KiB
TypeScript
/**
|
|
* One-time migration: PostgreSQL auth tables → JSON files
|
|
*
|
|
* Usage:
|
|
* POSTGRES_URL="postgres://..." bun run scripts/migrate-pg-to-files.ts
|
|
*
|
|
* Reads users and passkeys from Postgres, writes JSON files to {DATA_PATH}/auth/.
|
|
* Safe to run multiple times (overwrites files).
|
|
*/
|
|
|
|
import { join } from 'node:path';
|
|
import { mkdir } from 'node:fs/promises';
|
|
import postgres from 'postgres';
|
|
|
|
const POSTGRES_URL = process.env.POSTGRES_URL;
|
|
if (!POSTGRES_URL) {
|
|
console.error('POSTGRES_URL env var is required');
|
|
process.exit(1);
|
|
}
|
|
|
|
const DATA_PATH = process.env.DATA_PATH ?? join(process.cwd(), 'data');
|
|
const AUTH_DIR = join(DATA_PATH, 'auth');
|
|
|
|
const sql = postgres(POSTGRES_URL);
|
|
|
|
try {
|
|
await mkdir(AUTH_DIR, { recursive: true });
|
|
|
|
const users = await sql`SELECT id, email, password, role, status, name, username, avatar, password_changed_at FROM users ORDER BY id`;
|
|
const passkeys = await sql`SELECT id, email, origin, credential_id, public_key, counter FROM passkeys ORDER BY id`;
|
|
|
|
const mappedUsers = users.map((u) => ({
|
|
id: Number(u.id),
|
|
email: u.email,
|
|
password: u.password ?? null,
|
|
role: u.role ?? 'Member',
|
|
status: u.status ?? 'Unverified',
|
|
name: u.name ?? null,
|
|
username: u.username ?? null,
|
|
avatar: u.avatar ?? null,
|
|
passwordChangedAt: u.password_changed_at ? Number(u.password_changed_at) : null,
|
|
}));
|
|
|
|
const mappedPasskeys = passkeys.map((p) => ({
|
|
id: Number(p.id),
|
|
email: p.email,
|
|
origin: p.origin ?? null,
|
|
credentialId: p.credential_id ?? null,
|
|
publicKey: p.public_key ?? null,
|
|
counter: Number(p.counter ?? 0),
|
|
}));
|
|
|
|
const maxUserId = mappedUsers.reduce((max, u) => Math.max(max, u.id), 0);
|
|
const maxPasskeyId = mappedPasskeys.reduce((max, p) => Math.max(max, p.id), 0);
|
|
|
|
const meta = {
|
|
nextUserId: maxUserId + 1,
|
|
nextPasskeyId: maxPasskeyId + 1,
|
|
};
|
|
|
|
const write = (file: string, data: unknown) => Bun.write(join(AUTH_DIR, file), JSON.stringify(data, null, 2));
|
|
|
|
await Promise.all([
|
|
write('users.json', mappedUsers),
|
|
write('passkeys.json', mappedPasskeys),
|
|
write('passkey-challenges.json', []),
|
|
write('token-blacklist.json', []),
|
|
write('meta.json', meta),
|
|
]);
|
|
|
|
console.log(`Migrated ${mappedUsers.length} users, ${mappedPasskeys.length} passkeys`);
|
|
console.log(`Files written to ${AUTH_DIR}`);
|
|
console.log(`meta: nextUserId=${meta.nextUserId}, nextPasskeyId=${meta.nextPasskeyId}`);
|
|
} catch (err) {
|
|
console.error('Migration failed:', err);
|
|
process.exit(1);
|
|
} finally {
|
|
await sql.end();
|
|
}
|