From a33d799ee4784d7ae01f7f6d0ac03d3c617994e4 Mon Sep 17 00:00:00 2001 From: ilkin-overleaf <100852799+ilkin-overleaf@users.noreply.github.com> Date: Tue, 21 Oct 2025 15:44:00 +0300 Subject: [PATCH] Merge pull request #29002 from overleaf/ii-domain-capture-v1-field [web] Make subscription.v1_id unique GitOrigin-RevId: e65ea4b6bec54531cdc161ac02aee17d4a5cbdeb --- ...d_unique_constraint_subscription_v1_id.mjs | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 tools/migrations/20251010082205_add_unique_constraint_subscription_v1_id.mjs diff --git a/tools/migrations/20251010082205_add_unique_constraint_subscription_v1_id.mjs b/tools/migrations/20251010082205_add_unique_constraint_subscription_v1_id.mjs new file mode 100644 index 0000000000..5a6e37d2c3 --- /dev/null +++ b/tools/migrations/20251010082205_add_unique_constraint_subscription_v1_id.mjs @@ -0,0 +1,75 @@ +import Helpers from './lib/helpers.mjs' + +const tags = ['saas'] + +const originalIndexes = [ + { + key: { v1_id: 1 }, + name: 'v1_id_1', + sparse: true, + }, +] +const newIndexes = [ + { + key: { v1_id: 1 }, + name: 'v1_id_2', + unique: true, + partialFilterExpression: { + v1_id: { $type: 'number' }, + }, + }, +] + +async function assertNoDuplicateV1Ids(collection) { + const duplicates = await collection + .aggregate([ + { $match: { v1_id: { $exists: true, $ne: null } } }, + { + $group: { + _id: '$v1_id', + count: { $sum: 1 }, + docs: { $push: '$_id' }, + }, + }, + { $match: { count: { $gt: 1 } } }, + ]) + .toArray() + + if (duplicates.length > 0) { + const duplicateDetails = duplicates.map(dup => ({ + v1_id: dup._id, + count: dup.count, + docs: dup.docs, + })) + throw new Error( + `Duplicate v1_id values found. Migration aborted to prevent data loss. Details: ${JSON.stringify( + duplicateDetails, + null, + 2 + )}` + ) + } +} + +const migrate = async client => { + const { db } = client + + // pre‑check (keep old index intact if failing) + await assertNoDuplicateV1Ids(db.subscriptions) + await Helpers.dropIndexesFromCollection(db.subscriptions, originalIndexes) + await Helpers.addIndexesToCollection(db.subscriptions, newIndexes) +} + +const rollback = async client => { + const { db } = client + + await Helpers.dropIndexesFromCollection(db.subscriptions, newIndexes) + // recreate the original non-unique sparse index + await Helpers.addIndexesToCollection(db.subscriptions, originalIndexes) +} + +export default { + tags, + migrate, + rollback, +}