Skip to content

Commit 05158e5

Browse files
committed
Script to double check user-social-login data
1 parent 29238f0 commit 05158e5

File tree

2 files changed

+155
-0
lines changed

2 files changed

+155
-0
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,13 @@ The following table summarizes the environment variables used by the application
128128
| `JWT_SECRET` | Secret key for signing/verifying internal JWTs (e.g., 2FA, one-time tokens). | `just-a-random-string` (example) |
129129
| `LEGACY_BLOWFISH_KEY` | Base64 encoded Blowfish key for legacy password encryption/decryption. | `dGhpc2lzRGVmYXVmZlZhbHVl` (example) |
130130

131+
### Migrating legacy social login data
132+
133+
- Run `npx ts-node scripts/migrate-user-social-login.ts` to copy legacy `user_social_login` rows into `identity.user_social_login`.
134+
- Set `SOURCE_IDENTITY_PG_URL` (legacy) and `IDENTITY_DB_URL` (target) before running; `USER_SOCIAL_LOGIN_BATCH_SIZE` tunes pagination.
135+
- Flags available: `--dry-run` (log only), `--truncate` (clear target before load; ignored during dry-run), and `--insert-missing-only` (skip rows that already exist in the target).
136+
- Ensure `identity.social_login_provider` is migrated first so foreign keys resolve during import.
137+
131138

132139
**Downstream Usage**
133140
--------------------
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import { PrismaClient as TargetIdentityClient } from '@prisma/client';
2+
import { PrismaClient as SourceIdentityClient } from '../legacy_migrate/generated/source-identity';
3+
4+
const DEFAULT_BATCH_SIZE = 1000;
5+
6+
const parseBoolFlag = (flag: string) => process.argv.includes(flag);
7+
8+
const requiredEnv = (key: string) => {
9+
const value = process.env[key];
10+
if (!value) {
11+
throw new Error(`Missing required environment variable: ${key}`);
12+
}
13+
return value;
14+
};
15+
16+
async function migrateUserSocialLogin() {
17+
const dryRun = parseBoolFlag('--dry-run');
18+
const truncate = parseBoolFlag('--truncate');
19+
const batchSize = Number(process.env.USER_SOCIAL_LOGIN_BATCH_SIZE ?? DEFAULT_BATCH_SIZE);
20+
const insertMissingOnly = parseBoolFlag('--insert-missing-only');
21+
22+
if (Number.isNaN(batchSize) || batchSize <= 0) {
23+
throw new Error(`Invalid USER_SOCIAL_LOGIN_BATCH_SIZE: ${process.env.USER_SOCIAL_LOGIN_BATCH_SIZE}`);
24+
}
25+
26+
if (insertMissingOnly && truncate) {
27+
throw new Error('Cannot use --insert-missing-only together with --truncate');
28+
}
29+
30+
const sourceDbUrl = requiredEnv('SOURCE_IDENTITY_PG_URL');
31+
const targetDbUrl = requiredEnv('IDENTITY_DB_URL');
32+
33+
const sourceDb = new SourceIdentityClient({
34+
datasources: {
35+
db: { url: sourceDbUrl },
36+
},
37+
});
38+
39+
const targetDb = new TargetIdentityClient({
40+
datasources: {
41+
db: { url: targetDbUrl },
42+
},
43+
});
44+
45+
console.log(
46+
`Starting user_social_login migration (dryRun=${dryRun}, truncate=${truncate}, insertMissingOnly=${insertMissingOnly}, batchSize=${batchSize})`,
47+
);
48+
49+
try {
50+
const totalRows = await sourceDb.user_social_login.count();
51+
if (!totalRows) {
52+
console.log('No rows found in source user_social_login. Nothing to migrate.');
53+
return;
54+
}
55+
56+
console.log(`Found ${totalRows} rows to migrate from source user_social_login`);
57+
58+
if (truncate) {
59+
if (dryRun) {
60+
console.log('[Dry Run] Would truncate target identity.user_social_login');
61+
} else {
62+
console.log('Truncating target identity.user_social_login before import...');
63+
await targetDb.$executeRaw`TRUNCATE TABLE identity.user_social_login RESTART IDENTITY CASCADE`;
64+
}
65+
}
66+
67+
for (let offset = 0; offset < totalRows; offset += batchSize) {
68+
const batch = await sourceDb.user_social_login.findMany({
69+
orderBy: [{ user_id: 'asc' }, { social_login_provider_id: 'asc' }],
70+
skip: offset,
71+
take: batchSize,
72+
});
73+
74+
if (!batch.length) {
75+
break;
76+
}
77+
78+
if (dryRun) {
79+
console.log(`[Dry Run] Would migrate ${batch.length} rows (offset ${offset})`);
80+
continue;
81+
}
82+
83+
const recordsToInsert = batch.map((record) => ({
84+
social_user_id: record.social_user_id ?? null,
85+
user_id: Number(record.user_id),
86+
social_login_provider_id: Number(record.social_login_provider_id),
87+
social_user_name: record.social_user_name,
88+
social_email: record.social_email ?? null,
89+
social_email_verified: record.social_email_verified ?? null,
90+
create_date: record.create_date ?? undefined,
91+
modify_date: record.modify_date ?? undefined,
92+
}));
93+
94+
if (insertMissingOnly) {
95+
const existing = await targetDb.user_social_login.findMany({
96+
where: {
97+
OR: recordsToInsert.map((rec) => ({
98+
user_id: rec.user_id,
99+
social_login_provider_id: rec.social_login_provider_id,
100+
})),
101+
},
102+
select: {
103+
user_id: true,
104+
social_login_provider_id: true,
105+
},
106+
});
107+
108+
const existingKey = new Set(existing.map((row) => `${row.user_id}-${row.social_login_provider_id}`));
109+
const missing = recordsToInsert.filter(
110+
(rec) => !existingKey.has(`${rec.user_id}-${rec.social_login_provider_id}`),
111+
);
112+
113+
if (!missing.length) {
114+
console.log(`Batch at offset ${offset} skipped (all ${recordsToInsert.length} rows already present)`);
115+
continue;
116+
}
117+
118+
await targetDb.user_social_login.createMany({
119+
data: missing,
120+
skipDuplicates: true,
121+
});
122+
123+
console.log(
124+
`Migrated ${Math.min(offset + batch.length, totalRows)} / ${totalRows} rows (inserted ${missing.length}, skipped ${
125+
recordsToInsert.length - missing.length
126+
})`,
127+
);
128+
continue;
129+
}
130+
131+
await targetDb.user_social_login.createMany({
132+
data: recordsToInsert,
133+
skipDuplicates: true,
134+
});
135+
136+
console.log(`Migrated ${Math.min(offset + batch.length, totalRows)} / ${totalRows} rows`);
137+
}
138+
139+
console.log('user_social_login migration completed');
140+
} finally {
141+
await Promise.allSettled([sourceDb.$disconnect(), targetDb.$disconnect()]);
142+
}
143+
}
144+
145+
migrateUserSocialLogin().catch((error) => {
146+
console.error('user_social_login migration failed:', error);
147+
process.exit(1);
148+
});

0 commit comments

Comments
 (0)