""" Migration 0032 - Case-insensitive org name duplicates """ from uuid import UUID from pymongo.errors import DuplicateKeyError from btrixcloud.migrations import BaseMigration from btrixcloud.utils import slug_from_name MIGRATION_VERSION = "0032" class Migration(BaseMigration): """Migration class.""" # pylint: disable=unused-argument def __init__(self, mdb, **kwargs): super().__init__(mdb, migration_version=MIGRATION_VERSION) async def migrate_up(self): """Perform migration up. Check for case-insensitive duplicate org names and slugs. If found, rename org/slug as necessary to avoid duplicates regardless of case. """ orgs_db = self.mdb["organizations"] org_name_set = set() org_slug_set = set() cursor = orgs_db.find({}) async for org_dict in cursor: name = org_dict.get("name", "") slug = org_dict.get("slug", "") rename_org = False if name.lower() in org_name_set: rename_org = True else: org_name_set.add(name.lower()) if slug.lower() in org_slug_set: rename_org = True else: org_slug_set.add(slug.lower()) if rename_org: await self.update_org_name_and_slug( orgs_db, org_name_set, org_slug_set, name, org_dict.get("_id") ) # pylint: disable=too-many-arguments async def update_org_name_and_slug( self, orgs_db, org_name_set: set[str], org_slug_set: set[str], old_name: str, oid: UUID, ): """Rename org""" count = 2 suffix = f" {count}" while True: org_name = f"{old_name}{suffix}" org_slug = slug_from_name(org_name) if org_name.lower() in org_name_set or org_slug.lower() in org_slug_set: count += 1 suffix = f" {count}" continue try: await orgs_db.find_one_and_update( {"_id": oid}, {"$set": {"slug": org_slug, "name": org_name}} ) print( f"Renamed org {oid} to {org_name} with slug {org_slug}", flush=True ) break except DuplicateKeyError: # pylint: disable=raise-missing-from count += 1 suffix = f" {count}" # pylint: disable=broad-exception-caught except Exception as err: print(f"Error renaming org {oid}: {err}", flush=True) break