181 lines
4.7 KiB
JavaScript
181 lines
4.7 KiB
JavaScript
/**
|
|
* One-time migration: legacy Participant (single `event` ref) → tenantOrganization + eventMemberships[].
|
|
* Merges legacy rows that share the same (organization + email) into one document.
|
|
*
|
|
* Usage (from backend/): node scripts/migrate-participants-multi-event.js
|
|
*/
|
|
require('dotenv').config();
|
|
const mongoose = require('mongoose');
|
|
const Event = require('../src/models/Event');
|
|
|
|
const LEGACY_COLLECTION = 'participants';
|
|
|
|
function pickIdentity(doc) {
|
|
const keys = [
|
|
'email',
|
|
'firstName',
|
|
'lastName',
|
|
'displayName',
|
|
'phone',
|
|
'organization',
|
|
'designation',
|
|
'customFields',
|
|
'payment',
|
|
'notes',
|
|
'emailVerified',
|
|
];
|
|
const out = {};
|
|
for (const k of keys) {
|
|
if (doc[k] !== undefined) out[k] = doc[k];
|
|
}
|
|
return out;
|
|
}
|
|
|
|
function mergeIdentity(base, next) {
|
|
const out = { ...base };
|
|
for (const [k, v] of Object.entries(next)) {
|
|
if (v === undefined || v === null) continue;
|
|
if (typeof v === 'string' && !v.trim()) continue;
|
|
if (out[k] === undefined || out[k] === null || out[k] === '') {
|
|
out[k] = v;
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
async function main() {
|
|
const uri = process.env.MONGODB_URI || process.env.MONGO_URI;
|
|
if (!uri) {
|
|
console.error('Set MONGODB_URI or MONGO_URI');
|
|
process.exit(1);
|
|
}
|
|
await mongoose.connect(uri);
|
|
const coll = mongoose.connection.db.collection(LEGACY_COLLECTION);
|
|
|
|
const legacy = await coll
|
|
.find({
|
|
event: { $exists: true, $ne: null },
|
|
$or: [{ eventMemberships: { $exists: false } }, { eventMemberships: { $size: 0 } }],
|
|
})
|
|
.toArray();
|
|
|
|
console.log(`Found ${legacy.length} legacy participant documents`);
|
|
|
|
/** @type {Map<string, { keepId: any, docIds: any[], memberships: any[], identity: object }>} */
|
|
const emailGroups = new Map();
|
|
const singles = [];
|
|
|
|
for (const doc of legacy) {
|
|
const ev = await Event.findById(doc.event).lean();
|
|
if (!ev?.organization) {
|
|
console.warn('Skipping participant (no event org)', String(doc._id));
|
|
continue;
|
|
}
|
|
|
|
const membership = {
|
|
event: doc.event,
|
|
status: doc.status || 'pending',
|
|
checkedIn: Boolean(doc.checkedIn),
|
|
checkedInAt: doc.checkedInAt || undefined,
|
|
guestToken: doc.guestToken || undefined,
|
|
registrationDate: doc.registrationDate || doc.createdAt || new Date(),
|
|
tags: Array.isArray(doc.tags) ? [...doc.tags] : [],
|
|
};
|
|
|
|
const email = String(doc.email || '').trim().toLowerCase();
|
|
if (!email) {
|
|
singles.push({
|
|
docId: doc._id,
|
|
tenantOrganization: ev.organization,
|
|
membership,
|
|
identity: pickIdentity(doc),
|
|
});
|
|
continue;
|
|
}
|
|
|
|
const key = `${String(ev.organization)}|${email}`;
|
|
if (!emailGroups.has(key)) {
|
|
emailGroups.set(key, {
|
|
keepId: doc._id,
|
|
docIds: [],
|
|
memberships: [],
|
|
identity: pickIdentity(doc),
|
|
});
|
|
}
|
|
const g = emailGroups.get(key);
|
|
g.docIds.push(doc._id);
|
|
g.memberships.push(membership);
|
|
g.identity = mergeIdentity(g.identity, pickIdentity(doc));
|
|
if (String(doc._id) < String(g.keepId)) g.keepId = doc._id;
|
|
}
|
|
|
|
for (const [, g] of emailGroups) {
|
|
const ev0 = await Event.findById(g.memberships[0].event).lean();
|
|
const tenantOrganization = ev0?.organization;
|
|
if (!tenantOrganization) continue;
|
|
|
|
const byEvent = new Map();
|
|
for (const m of g.memberships) {
|
|
byEvent.set(String(m.event), m);
|
|
}
|
|
const eventMemberships = [...byEvent.values()];
|
|
|
|
await coll.updateOne(
|
|
{ _id: g.keepId },
|
|
{
|
|
$set: {
|
|
...g.identity,
|
|
email: String(g.identity.email || '').trim().toLowerCase(),
|
|
tenantOrganization,
|
|
eventMemberships,
|
|
},
|
|
$unset: {
|
|
event: '',
|
|
status: '',
|
|
checkedIn: '',
|
|
checkedInAt: '',
|
|
guestToken: '',
|
|
registrationDate: '',
|
|
tags: '',
|
|
},
|
|
}
|
|
);
|
|
const others = g.docIds.filter((id) => String(id) !== String(g.keepId));
|
|
|
|
if (others.length) {
|
|
await coll.deleteMany({ _id: { $in: others } });
|
|
console.log('Merged duplicates:', others.length, 'removed; kept', String(g.keepId));
|
|
}
|
|
}
|
|
|
|
for (const s of singles) {
|
|
await coll.updateOne(
|
|
{ _id: s.docId },
|
|
{
|
|
$set: {
|
|
...s.identity,
|
|
tenantOrganization: s.tenantOrganization,
|
|
eventMemberships: [s.membership],
|
|
},
|
|
$unset: {
|
|
event: '',
|
|
status: '',
|
|
checkedIn: '',
|
|
checkedInAt: '',
|
|
guestToken: '',
|
|
registrationDate: '',
|
|
tags: '',
|
|
},
|
|
}
|
|
);
|
|
}
|
|
|
|
console.log('Done. Email groups:', emailGroups.size, 'guest rows:', singles.length);
|
|
await mongoose.disconnect();
|
|
}
|
|
|
|
main().catch((e) => {
|
|
console.error(e);
|
|
process.exit(1);
|
|
});
|