/** * Backfill tenant fields for multi-tenant consistency (run BEFORE deploying required schema). * - Form.tenantOrganization from linked Event.organization * - FormSubmission.tenantOrganization from parent Form * - Logs Speaker rows missing organization (fix manually or delete) * * Run: cd backend && npm run backfill:tenant-organization */ const path = require('path'); require('dotenv').config({ path: path.join(__dirname, '..', '.env') }); const mongoose = require('mongoose'); const Form = require(path.join(__dirname, '..', 'src', 'models', 'Form')); const FormSubmission = require(path.join(__dirname, '..', 'src', 'models', 'FormSubmission')); const Event = require(path.join(__dirname, '..', 'src', 'models', 'Event')); const Speaker = require(path.join(__dirname, '..', 'src', 'models', 'Speaker')); async function run() { const uri = process.env.MONGODB_URI; if (!uri) { console.error('MONGODB_URI is not set'); process.exit(1); } await mongoose.connect(uri); const formsNeedingTenant = await Form.find({ $or: [{ tenantOrganization: { $exists: false } }, { tenantOrganization: null }], }) .select('_id event') .lean(); let formsUpdated = 0; for (const f of formsNeedingTenant) { if (!f.event) { console.warn(`Form ${f._id} has no event and no tenantOrganization — skipped`); continue; } const ev = await Event.findById(f.event).select('organization').lean(); if (!ev?.organization) { console.warn(`Form ${f._id} event ${f.event} has no organization — skipped`); continue; } await Form.updateOne({ _id: f._id }, { $set: { tenantOrganization: ev.organization } }); formsUpdated += 1; } console.log(`Backfilled tenantOrganization on ${formsUpdated} form(s).`); const subsNeedingTenant = await FormSubmission.find({ $or: [{ tenantOrganization: { $exists: false } }, { tenantOrganization: null }], }) .select('_id form') .lean(); let subsUpdated = 0; for (const s of subsNeedingTenant) { const form = await Form.findById(s.form).select('tenantOrganization').lean(); if (!form?.tenantOrganization) { console.warn(`Submission ${s._id} form ${s.form} has no tenantOrganization — skipped`); continue; } await FormSubmission.updateOne({ _id: s._id }, { $set: { tenantOrganization: form.tenantOrganization } }); subsUpdated += 1; } console.log(`Backfilled tenantOrganization on ${subsUpdated} form submission(s).`); const orphanSpeakers = await Speaker.countDocuments({ $or: [{ organization: { $exists: false } }, { organization: null }], }); if (orphanSpeakers > 0) { console.warn( `${orphanSpeakers} speaker(s) have no organization — assign org or delete before schema requires it.`, ); const sample = await Speaker.find({ $or: [{ organization: { $exists: false } }, { organization: null }] }) .select('_id email firstName lastName') .limit(10) .lean(); console.warn('Sample:', JSON.stringify(sample, null, 2)); } else { console.log('All speakers have an organization.'); } await mongoose.disconnect(); console.log('Done.'); } run().catch((e) => { console.error(e); process.exit(1); });