112 lines
3.9 KiB
TypeScript
112 lines
3.9 KiB
TypeScript
/**
|
|||
|
|
* Copies legacy profile.settings.emailNotifications into
|
||
|
|
* organizationmember_email_notifications (one row per membership), then
|
||
|
|
* removes the old jsonb key from profile.settings.
|
||
|
|
*
|
||
|
|
* Usage:
|
||
|
|
* pnpm db:backfill-member-email-notifications # dry run (default)
|
||
|
|
* pnpm db:backfill-member-email-notifications -- --execute # apply updates
|
||
|
|
*/
|
||
|
|
import 'dotenv/config';
|
||
|
|
|
||
|
|
import postgres from 'postgres';
|
||
|
|
|
||
|
|
const connectionString = process.env.DATABASE_URL ?? process.env.PRIVATE_DATABASE_URL ?? '';
|
||
|
|
const shouldExecute = process.argv.includes('--execute');
|
||
|
|
|
||
|
|
if (!connectionString) {
|
||
|
|
console.error('DATABASE_URL or PRIVATE_DATABASE_URL environment variable is required');
|
||
|
|
process.exit(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
const AFFECTED_MEMBERSHIPS_SQL = `
|
||
|
|
SELECT
|
||
|
|
om.id AS organization_member_id,
|
||
|
|
p.id AS profile_id,
|
||
|
|
om.organization_id,
|
||
|
|
p.settings->'emailNotifications' AS legacy_preferences
|
||
|
|
FROM organizationmember om
|
||
|
|
INNER JOIN profile p ON p.id = om.profile_id
|
||
|
|
WHERE p.settings ? 'emailNotifications'
|
||
|
|
`;
|
||
|
|
|
||
|
|
async function main() {
|
||
|
|
const sql = postgres(connectionString);
|
||
|
|
|
||
|
|
try {
|
||
|
|
const affected = await sql.unsafe(AFFECTED_MEMBERSHIPS_SQL);
|
||
|
|
console.log(`Found ${affected.length} organization membership(s) with legacy profile email preferences.`);
|
||
|
|
|
||
|
|
if (affected.length === 0) {
|
||
|
|
console.log('Nothing to do.');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!shouldExecute) {
|
||
|
|
console.log('Dry run only. Re-run with --execute to apply updates.');
|
||
|
|
console.log('Sample (up to 10):');
|
||
|
|
for (const row of affected.slice(0, 10)) {
|
||
|
|
console.log(` member=${row.organization_member_id} profile=${row.profile_id} org=${row.organization_id}`);
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const inserted = await sql`
|
||
|
|
INSERT INTO organizationmember_email_notifications (
|
||
|
|
organization_member_id,
|
||
|
|
new_student,
|
||
|
|
new_submission,
|
||
|
|
grading_result,
|
||
|
|
newsfeed,
|
||
|
|
quiz_assigned,
|
||
|
|
cohort_reminder,
|
||
|
|
session,
|
||
|
|
course_completion
|
||
|
|
)
|
||
|
|
SELECT
|
||
|
|
om.id,
|
||
|
|
COALESCE((p.settings->'emailNotifications'->>'newStudent')::boolean, true),
|
||
|
|
COALESCE((p.settings->'emailNotifications'->>'newSubmission')::boolean, true),
|
||
|
|
COALESCE((p.settings->'emailNotifications'->>'gradingResult')::boolean, true),
|
||
|
|
COALESCE((p.settings->'emailNotifications'->>'newsfeed')::boolean, true),
|
||
|
|
COALESCE((p.settings->'emailNotifications'->>'quizAssigned')::boolean, true),
|
||
|
|
COALESCE((p.settings->'emailNotifications'->>'cohortReminder')::boolean, true),
|
||
|
|
COALESCE((p.settings->'emailNotifications'->>'session')::boolean, true),
|
||
|
|
COALESCE((p.settings->'emailNotifications'->>'courseCompletion')::boolean, true)
|
||
|
|
FROM organizationmember om
|
||
|
|
INNER JOIN profile p ON p.id = om.profile_id
|
||
|
|
WHERE p.settings ? 'emailNotifications'
|
||
|
|
ON CONFLICT (organization_member_id) DO NOTHING
|
||
|
|
RETURNING organization_member_id
|
||
|
|
`;
|
||
|
|
|
||
|
|
const clearedProfiles = await sql`
|
||
|
|
UPDATE profile
|
||
|
|
SET settings = settings - 'emailNotifications'
|
||
|
|
WHERE settings ? 'emailNotifications'
|
||
|
|
RETURNING id
|
||
|
|
`;
|
||
|
|
|
||
|
|
const remaining = await sql.unsafe(`SELECT COUNT(*)::int AS count FROM (${AFFECTED_MEMBERSHIPS_SQL}) AS affected`);
|
||
|
|
const remainingCount = remaining[0]?.count ?? 0;
|
||
|
|
|
||
|
|
console.log(`Inserted ${inserted.length} organizationmember_email_notifications row(s).`);
|
||
|
|
console.log(`Cleared legacy preferences from ${clearedProfiles.length} profile(s).`);
|
||
|
|
console.log(`Remaining memberships with legacy profile preferences: ${remainingCount}`);
|
||
|
|
|
||
|
|
if (remainingCount > 0) {
|
||
|
|
console.warn('Some rows were not migrated — inspect manually before retrying.');
|
||
|
|
process.exit(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
console.log('Backfill complete.');
|
||
|
|
} catch (error) {
|
||
|
|
console.error('backfill-member-email-notifications-from-profile error:', error);
|
||
|
|
throw error;
|
||
|
|
} finally {
|
||
|
|
await sql.end();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
main();
|