From 667193403c89710243875f7a7d435a11ff16a5b8 Mon Sep 17 00:00:00 2001 From: Hariom Vashista Date: Mon, 22 Sep 2025 20:09:51 +0530 Subject: [PATCH 1/7] fix/user-roles: add migration script to backfill user role details --- .../commands/migrate_add_roles_to_teams.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 todo/management/commands/migrate_add_roles_to_teams.py diff --git a/todo/management/commands/migrate_add_roles_to_teams.py b/todo/management/commands/migrate_add_roles_to_teams.py new file mode 100644 index 00000000..1760cbbf --- /dev/null +++ b/todo/management/commands/migrate_add_roles_to_teams.py @@ -0,0 +1,43 @@ +from django.core.management.base import BaseCommand +from todo.repositories.user_role_repository import UserRoleRepository +from todo.repositories.team_repository import TeamRepository, UserTeamDetailsRepository +from todo.constants.role import RoleScope, RoleName + + +class Command(BaseCommand): + help = "Backfill user_roles so roles are present for every team member and fix incorrect assigned roles" + + def handle(self, *args, **options): + self.stdout.write(self.style.WARNING("\n--- Starting Team Roles Fix Script ---")) + teams_data = TeamRepository.get_collection().find({}) + roles_scope = RoleScope.TEAM.value + + roles_ensured = 0 + roles_deactivated = 0 + for team in teams_data: + team_id = str(team["_id"]) + team_owner = team["created_by"] + team_members = list(UserTeamDetailsRepository.get_by_team_id(team_id)) + for member in team_members: + member_id = str(member.user_id) + if member_id == team_owner: + for role in RoleName: + if role.value != RoleName.MODERATOR.value: + result = UserRoleRepository.assign_role(member_id, role.value, roles_scope, team_id) + roles_ensured += 1 + else: + user_roles = UserRoleRepository.get_user_roles(member_id, roles_scope, team_id) + has_member_role = False + for role in user_roles: + if role.role_name == RoleName.MEMBER.value: + has_member_role = True + else: + result = UserRoleRepository.remove_role_by_id(member_id, str(role.id), roles_scope, team_id) + if result: + roles_deactivated += 1 + if not has_member_role: + UserRoleRepository.assign_role(member_id, RoleName.MEMBER.value, roles_scope, team_id) + roles_ensured += 1 + + self.stdout.write(self.style.SUCCESS(f"Roles Ensured (Created or Already Existed): {roles_ensured}")) + self.stdout.write(self.style.SUCCESS(f"Incorrect Roles Removed: {roles_deactivated}")) From 8ee0e743e934bf2997f7fd97d1cb04300e4d8548 Mon Sep 17 00:00:00 2001 From: Hariom Vashista Date: Tue, 23 Sep 2025 00:15:30 +0530 Subject: [PATCH 2/7] fix(user-roles): solve n+1 query problem by using bulk operations --- .../commands/migrate_add_roles_to_teams.py | 102 +++++++++++++++--- 1 file changed, 87 insertions(+), 15 deletions(-) diff --git a/todo/management/commands/migrate_add_roles_to_teams.py b/todo/management/commands/migrate_add_roles_to_teams.py index 1760cbbf..4fe104ad 100644 --- a/todo/management/commands/migrate_add_roles_to_teams.py +++ b/todo/management/commands/migrate_add_roles_to_teams.py @@ -2,18 +2,25 @@ from todo.repositories.user_role_repository import UserRoleRepository from todo.repositories.team_repository import TeamRepository, UserTeamDetailsRepository from todo.constants.role import RoleScope, RoleName - +from datetime import datetime, timezone +from todo.services.enhanced_dual_write_service import EnhancedDualWriteService class Command(BaseCommand): help = "Backfill user_roles so roles are present for every team member and fix incorrect assigned roles" def handle(self, *args, **options): self.stdout.write(self.style.WARNING("\n--- Starting Team Roles Fix Script ---")) - teams_data = TeamRepository.get_collection().find({}) + teams_data = TeamRepository.get_collection().find({"is_deleted": False}) roles_scope = RoleScope.TEAM.value roles_ensured = 0 roles_deactivated = 0 + dual_write_service = EnhancedDualWriteService() + + roles_to_assign = [] + roles_to_remove = [] + postgres_operations = [] + for team in teams_data: team_id = str(team["_id"]) team_owner = team["created_by"] @@ -21,23 +28,88 @@ def handle(self, *args, **options): for member in team_members: member_id = str(member.user_id) if member_id == team_owner: - for role in RoleName: - if role.value != RoleName.MODERATOR.value: - result = UserRoleRepository.assign_role(member_id, role.value, roles_scope, team_id) - roles_ensured += 1 + member_roles = UserRoleRepository.get_user_roles(member_id, roles_scope, team_id) + existing_roles = [role.role_name for role in member_roles] + for role in (RoleName.OWNER.value, RoleName.ADMIN.value, RoleName.MEMBER.value): + if role not in existing_roles: + roles_to_assign.append({ + "user_id": member_id, + "role_name": role, + "scope": roles_scope, + "team_id": team_id, + "is_active": True, + "created_at": datetime.now(timezone.utc), + "created_by": "system" + }) else: - user_roles = UserRoleRepository.get_user_roles(member_id, roles_scope, team_id) + member_roles = UserRoleRepository.get_user_roles(member_id, roles_scope, team_id) has_member_role = False - for role in user_roles: + for role in member_roles: if role.role_name == RoleName.MEMBER.value: has_member_role = True else: - result = UserRoleRepository.remove_role_by_id(member_id, str(role.id), roles_scope, team_id) - if result: - roles_deactivated += 1 + roles_to_remove.append({ + "mongo_id": role.id, + "user_id": member_id, + "role_name": role.role_name, + "scope": roles_scope, + "team_id": role.team_id, + "is_active": role.is_active, + "created_by": role.created_by, + "created_at": role.created_at + }) if not has_member_role: - UserRoleRepository.assign_role(member_id, RoleName.MEMBER.value, roles_scope, team_id) - roles_ensured += 1 + roles_to_assign.append({ + "user_id": member_id, + "role_name": RoleName.MEMBER.value, + "scope": roles_scope, + "team_id": team_id, + "is_active": True, + "created_at": datetime.now(timezone.utc), + "created_by": "system" + }) + + if roles_to_assign: + result = UserRoleRepository.get_collection().insert_many(roles_to_assign) + roles_ensured = len(result.inserted_ids) + self.stdout.write(self.style.SUCCESS(f"Successfully inserted {roles_ensured} new roles.")) + + for role_data, mongo_id in zip(roles_to_assign, result.inserted_ids): + postgres_operations.append({ + "operation": "create", + "collection_name": "user_roles", + "mongo_id": str(mongo_id), + "data": role_data + }) - self.stdout.write(self.style.SUCCESS(f"Roles Ensured (Created or Already Existed): {roles_ensured}")) - self.stdout.write(self.style.SUCCESS(f"Incorrect Roles Removed: {roles_deactivated}")) + if roles_to_remove: + for role in roles_to_remove: + mongo_id = str(role["mongo_id"]) + postgres_operations.append({ + "operation": "update", + "collection_name": "user_roles", + "mongo_id": mongo_id, + "data": { + "user_id": role["user_id"], + "role_name": role["role_name"], + "scope": role["scope"], + "team_id": role["team_id"], + "is_active": False, + "created_at": role["created_at"], + "created_by": role["created_by"] + } + }) + role_ids_to_remove = [roles["mongo_id"] for roles in roles_to_remove] + result = UserRoleRepository.get_collection().update_many( + {"_id": {"$in": role_ids_to_remove}}, + {"$set": {"is_active": False}} + ) + roles_deactivated = result.modified_count + self.stdout.write(self.style.SUCCESS(f"Successfully deactivated {roles_deactivated} roles.")) + if postgres_operations: + self.stdout.write(self.style.WARNING(f"\nStarting sync of {len(postgres_operations)} operations to PostgreSQL...")) + success = dual_write_service.batch_operations(postgres_operations) + if success: + self.stdout.write(self.style.SUCCESS("PostgreSQL sync completed successfully.")) + else: + self.stdout.write(self.style.ERROR("PostgreSQL sync encountered errors. Check logs for details.")) From 0b7dcb6b2f8954f7e96598cdbbc75c225c8c4173 Mon Sep 17 00:00:00 2001 From: Hariom Vashista Date: Tue, 23 Sep 2025 00:30:08 +0530 Subject: [PATCH 3/7] fix(user-role-backfill): fix formatting issue --- .../commands/migrate_add_roles_to_teams.py | 120 ++++++++++-------- 1 file changed, 66 insertions(+), 54 deletions(-) diff --git a/todo/management/commands/migrate_add_roles_to_teams.py b/todo/management/commands/migrate_add_roles_to_teams.py index 4fe104ad..4c890a1f 100644 --- a/todo/management/commands/migrate_add_roles_to_teams.py +++ b/todo/management/commands/migrate_add_roles_to_teams.py @@ -5,6 +5,7 @@ from datetime import datetime, timezone from todo.services.enhanced_dual_write_service import EnhancedDualWriteService + class Command(BaseCommand): help = "Backfill user_roles so roles are present for every team member and fix incorrect assigned roles" @@ -16,11 +17,11 @@ def handle(self, *args, **options): roles_ensured = 0 roles_deactivated = 0 dual_write_service = EnhancedDualWriteService() - + roles_to_assign = [] roles_to_remove = [] postgres_operations = [] - + for team in teams_data: team_id = str(team["_id"]) team_owner = team["created_by"] @@ -32,15 +33,17 @@ def handle(self, *args, **options): existing_roles = [role.role_name for role in member_roles] for role in (RoleName.OWNER.value, RoleName.ADMIN.value, RoleName.MEMBER.value): if role not in existing_roles: - roles_to_assign.append({ - "user_id": member_id, - "role_name": role, - "scope": roles_scope, - "team_id": team_id, - "is_active": True, - "created_at": datetime.now(timezone.utc), - "created_by": "system" - }) + roles_to_assign.append( + { + "user_id": member_id, + "role_name": role, + "scope": roles_scope, + "team_id": team_id, + "is_active": True, + "created_at": datetime.now(timezone.utc), + "created_by": "system", + } + ) else: member_roles = UserRoleRepository.get_user_roles(member_id, roles_scope, team_id) has_member_role = False @@ -48,66 +51,75 @@ def handle(self, *args, **options): if role.role_name == RoleName.MEMBER.value: has_member_role = True else: - roles_to_remove.append({ - "mongo_id": role.id, + roles_to_remove.append( + { + "mongo_id": role.id, + "user_id": member_id, + "role_name": role.role_name, + "scope": roles_scope, + "team_id": role.team_id, + "is_active": role.is_active, + "created_by": role.created_by, + "created_at": role.created_at, + } + ) + if not has_member_role: + roles_to_assign.append( + { "user_id": member_id, - "role_name": role.role_name, + "role_name": RoleName.MEMBER.value, "scope": roles_scope, - "team_id": role.team_id, - "is_active": role.is_active, - "created_by": role.created_by, - "created_at": role.created_at - }) - if not has_member_role: - roles_to_assign.append({ - "user_id": member_id, - "role_name": RoleName.MEMBER.value, - "scope": roles_scope, - "team_id": team_id, - "is_active": True, - "created_at": datetime.now(timezone.utc), - "created_by": "system" - }) - + "team_id": team_id, + "is_active": True, + "created_at": datetime.now(timezone.utc), + "created_by": "system", + } + ) + if roles_to_assign: result = UserRoleRepository.get_collection().insert_many(roles_to_assign) roles_ensured = len(result.inserted_ids) self.stdout.write(self.style.SUCCESS(f"Successfully inserted {roles_ensured} new roles.")) - + for role_data, mongo_id in zip(roles_to_assign, result.inserted_ids): - postgres_operations.append({ - "operation": "create", - "collection_name": "user_roles", - "mongo_id": str(mongo_id), - "data": role_data - }) + postgres_operations.append( + { + "operation": "create", + "collection_name": "user_roles", + "mongo_id": str(mongo_id), + "data": role_data, + } + ) if roles_to_remove: for role in roles_to_remove: mongo_id = str(role["mongo_id"]) - postgres_operations.append({ - "operation": "update", - "collection_name": "user_roles", - "mongo_id": mongo_id, - "data": { - "user_id": role["user_id"], - "role_name": role["role_name"], - "scope": role["scope"], - "team_id": role["team_id"], - "is_active": False, - "created_at": role["created_at"], - "created_by": role["created_by"] + postgres_operations.append( + { + "operation": "update", + "collection_name": "user_roles", + "mongo_id": mongo_id, + "data": { + "user_id": role["user_id"], + "role_name": role["role_name"], + "scope": role["scope"], + "team_id": role["team_id"], + "is_active": False, + "created_at": role["created_at"], + "created_by": role["created_by"], + }, } - }) - role_ids_to_remove = [roles["mongo_id"] for roles in roles_to_remove] + ) + role_ids_to_remove = [roles["mongo_id"] for roles in roles_to_remove] result = UserRoleRepository.get_collection().update_many( - {"_id": {"$in": role_ids_to_remove}}, - {"$set": {"is_active": False}} + {"_id": {"$in": role_ids_to_remove}}, {"$set": {"is_active": False}} ) roles_deactivated = result.modified_count self.stdout.write(self.style.SUCCESS(f"Successfully deactivated {roles_deactivated} roles.")) if postgres_operations: - self.stdout.write(self.style.WARNING(f"\nStarting sync of {len(postgres_operations)} operations to PostgreSQL...")) + self.stdout.write( + self.style.WARNING(f"\nStarting sync of {len(postgres_operations)} operations to PostgreSQL...") + ) success = dual_write_service.batch_operations(postgres_operations) if success: self.stdout.write(self.style.SUCCESS("PostgreSQL sync completed successfully.")) From 0a8d0d7ef07cc8ab03a076fd933bbafa82d02b19 Mon Sep 17 00:00:00 2001 From: Hariom Vashista Date: Tue, 23 Sep 2025 14:19:56 +0530 Subject: [PATCH 4/7] fix(user-role):add migration file to backfill data --- .../commands/migrate_add_roles_to_teams.py | 127 ---------------- .../0004_backfill_user_role_data.py | 135 ++++++++++++++++++ 2 files changed, 135 insertions(+), 127 deletions(-) delete mode 100644 todo/management/commands/migrate_add_roles_to_teams.py create mode 100644 todo/migrations/0004_backfill_user_role_data.py diff --git a/todo/management/commands/migrate_add_roles_to_teams.py b/todo/management/commands/migrate_add_roles_to_teams.py deleted file mode 100644 index 4c890a1f..00000000 --- a/todo/management/commands/migrate_add_roles_to_teams.py +++ /dev/null @@ -1,127 +0,0 @@ -from django.core.management.base import BaseCommand -from todo.repositories.user_role_repository import UserRoleRepository -from todo.repositories.team_repository import TeamRepository, UserTeamDetailsRepository -from todo.constants.role import RoleScope, RoleName -from datetime import datetime, timezone -from todo.services.enhanced_dual_write_service import EnhancedDualWriteService - - -class Command(BaseCommand): - help = "Backfill user_roles so roles are present for every team member and fix incorrect assigned roles" - - def handle(self, *args, **options): - self.stdout.write(self.style.WARNING("\n--- Starting Team Roles Fix Script ---")) - teams_data = TeamRepository.get_collection().find({"is_deleted": False}) - roles_scope = RoleScope.TEAM.value - - roles_ensured = 0 - roles_deactivated = 0 - dual_write_service = EnhancedDualWriteService() - - roles_to_assign = [] - roles_to_remove = [] - postgres_operations = [] - - for team in teams_data: - team_id = str(team["_id"]) - team_owner = team["created_by"] - team_members = list(UserTeamDetailsRepository.get_by_team_id(team_id)) - for member in team_members: - member_id = str(member.user_id) - if member_id == team_owner: - member_roles = UserRoleRepository.get_user_roles(member_id, roles_scope, team_id) - existing_roles = [role.role_name for role in member_roles] - for role in (RoleName.OWNER.value, RoleName.ADMIN.value, RoleName.MEMBER.value): - if role not in existing_roles: - roles_to_assign.append( - { - "user_id": member_id, - "role_name": role, - "scope": roles_scope, - "team_id": team_id, - "is_active": True, - "created_at": datetime.now(timezone.utc), - "created_by": "system", - } - ) - else: - member_roles = UserRoleRepository.get_user_roles(member_id, roles_scope, team_id) - has_member_role = False - for role in member_roles: - if role.role_name == RoleName.MEMBER.value: - has_member_role = True - else: - roles_to_remove.append( - { - "mongo_id": role.id, - "user_id": member_id, - "role_name": role.role_name, - "scope": roles_scope, - "team_id": role.team_id, - "is_active": role.is_active, - "created_by": role.created_by, - "created_at": role.created_at, - } - ) - if not has_member_role: - roles_to_assign.append( - { - "user_id": member_id, - "role_name": RoleName.MEMBER.value, - "scope": roles_scope, - "team_id": team_id, - "is_active": True, - "created_at": datetime.now(timezone.utc), - "created_by": "system", - } - ) - - if roles_to_assign: - result = UserRoleRepository.get_collection().insert_many(roles_to_assign) - roles_ensured = len(result.inserted_ids) - self.stdout.write(self.style.SUCCESS(f"Successfully inserted {roles_ensured} new roles.")) - - for role_data, mongo_id in zip(roles_to_assign, result.inserted_ids): - postgres_operations.append( - { - "operation": "create", - "collection_name": "user_roles", - "mongo_id": str(mongo_id), - "data": role_data, - } - ) - - if roles_to_remove: - for role in roles_to_remove: - mongo_id = str(role["mongo_id"]) - postgres_operations.append( - { - "operation": "update", - "collection_name": "user_roles", - "mongo_id": mongo_id, - "data": { - "user_id": role["user_id"], - "role_name": role["role_name"], - "scope": role["scope"], - "team_id": role["team_id"], - "is_active": False, - "created_at": role["created_at"], - "created_by": role["created_by"], - }, - } - ) - role_ids_to_remove = [roles["mongo_id"] for roles in roles_to_remove] - result = UserRoleRepository.get_collection().update_many( - {"_id": {"$in": role_ids_to_remove}}, {"$set": {"is_active": False}} - ) - roles_deactivated = result.modified_count - self.stdout.write(self.style.SUCCESS(f"Successfully deactivated {roles_deactivated} roles.")) - if postgres_operations: - self.stdout.write( - self.style.WARNING(f"\nStarting sync of {len(postgres_operations)} operations to PostgreSQL...") - ) - success = dual_write_service.batch_operations(postgres_operations) - if success: - self.stdout.write(self.style.SUCCESS("PostgreSQL sync completed successfully.")) - else: - self.stdout.write(self.style.ERROR("PostgreSQL sync encountered errors. Check logs for details.")) diff --git a/todo/migrations/0004_backfill_user_role_data.py b/todo/migrations/0004_backfill_user_role_data.py new file mode 100644 index 00000000..06e7f5cd --- /dev/null +++ b/todo/migrations/0004_backfill_user_role_data.py @@ -0,0 +1,135 @@ +# Generated by Django 5.1.5 on 2025-09-23 07:25 + +from django.db import migrations +import logging +from todo.repositories.user_role_repository import UserRoleRepository +from todo.repositories.team_repository import TeamRepository, UserTeamDetailsRepository +from todo.constants.role import RoleScope, RoleName +from datetime import datetime, timezone +from todo.services.enhanced_dual_write_service import EnhancedDualWriteService + +logger = logging.getLogger(__name__) + + +def fix_team_roles(apps, schema_editor): + logger.info("\n--- Starting Team Roles Fix Script ---") + + teams_data = TeamRepository.get_collection().find({"is_deleted": False}) + roles_scope = RoleScope.TEAM.value + + roles_ensured = 0 + roles_deactivated = 0 + dual_write_service = EnhancedDualWriteService() + + roles_to_assign = [] + roles_to_remove = [] + postgres_operations = [] + + for team in teams_data: + team_id = str(team["_id"]) + team_owner = team["created_by"] + team_members = list(UserTeamDetailsRepository.get_by_team_id(team_id)) + for member in team_members: + member_id = str(member.user_id) + if member_id == team_owner: + member_roles = UserRoleRepository.get_user_roles(member_id, roles_scope, team_id) + existing_roles = [role.role_name for role in member_roles] + for role in (RoleName.OWNER.value, RoleName.ADMIN.value, RoleName.MEMBER.value): + if role not in existing_roles: + roles_to_assign.append( + { + "user_id": member_id, + "role_name": role, + "scope": roles_scope, + "team_id": team_id, + "is_active": True, + "created_at": datetime.now(timezone.utc), + "created_by": "system", + } + ) + else: + member_roles = UserRoleRepository.get_user_roles(member_id, roles_scope, team_id) + has_member_role = False + for role in member_roles: + if role.role_name == RoleName.MEMBER.value: + has_member_role = True + else: + roles_to_remove.append( + { + "mongo_id": role.id, + "user_id": member_id, + "role_name": role.role_name, + "scope": roles_scope, + "team_id": role.team_id, + "is_active": role.is_active, + "created_by": role.created_by, + "created_at": role.created_at, + } + ) + if not has_member_role: + roles_to_assign.append( + { + "user_id": member_id, + "role_name": RoleName.MEMBER.value, + "scope": roles_scope, + "team_id": team_id, + "is_active": True, + "created_at": datetime.now(timezone.utc), + "created_by": "system", + } + ) + if roles_to_assign: + result = UserRoleRepository.get_collection().insert_many(roles_to_assign) + roles_ensured = len(result.inserted_ids) + logger.info(f"Successfully inserted {roles_ensured} new roles.") + + for role_data, mongo_id in zip(roles_to_assign, result.inserted_ids): + postgres_operations.append( + { + "operation": "create", + "collection_name": "user_roles", + "mongo_id": str(mongo_id), + "data": role_data, + } + ) + + if roles_to_remove: + for role in roles_to_remove: + mongo_id = str(role["mongo_id"]) + postgres_operations.append( + { + "operation": "update", + "collection_name": "user_roles", + "mongo_id": mongo_id, + "data": { + "user_id": role["user_id"], + "role_name": role["role_name"], + "scope": role["scope"], + "team_id": role["team_id"], + "is_active": False, + "created_at": role["created_at"], + "created_by": role["created_by"], + }, + } + ) + role_ids_to_remove = [roles["mongo_id"] for roles in roles_to_remove] + result = UserRoleRepository.get_collection().update_many( + {"_id": {"$in": role_ids_to_remove}}, {"$set": {"is_active": False}} + ) + roles_deactivated = result.modified_count + logger.info(f"Successfully deactivated {roles_deactivated} roles.") + if postgres_operations: + logger.info(f"\nStarting sync of {len(postgres_operations)} operations to PostgreSQL...") + success = dual_write_service.batch_operations(postgres_operations) + if success: + logger.info("PostgreSQL sync completed successfully.") + else: + logger.warning("PostgreSQL sync encountered errors. Check logs for details.") + + +class Migration(migrations.Migration): + dependencies = [ + ("todo", "0003_alter_postgresuserrole_unique_together_and_more"), + ] + + operations = [migrations.RunPython(fix_team_roles, migrations.RunPython.noop)] From ed1017e0d95d0375e226e0ee77548f6d7ba5939e Mon Sep 17 00:00:00 2001 From: Hariom Vashista Date: Tue, 23 Sep 2025 14:38:18 +0530 Subject: [PATCH 5/7] fix(user-roles): replace management command with migration script - ignore migration script in test env --- .../commands/migrate_add_roles_to_teams.py | 127 ---------------- .../0004_backfill_user_role_data.py | 138 ++++++++++++++++++ 2 files changed, 138 insertions(+), 127 deletions(-) delete mode 100644 todo/management/commands/migrate_add_roles_to_teams.py create mode 100644 todo/migrations/0004_backfill_user_role_data.py diff --git a/todo/management/commands/migrate_add_roles_to_teams.py b/todo/management/commands/migrate_add_roles_to_teams.py deleted file mode 100644 index 4c890a1f..00000000 --- a/todo/management/commands/migrate_add_roles_to_teams.py +++ /dev/null @@ -1,127 +0,0 @@ -from django.core.management.base import BaseCommand -from todo.repositories.user_role_repository import UserRoleRepository -from todo.repositories.team_repository import TeamRepository, UserTeamDetailsRepository -from todo.constants.role import RoleScope, RoleName -from datetime import datetime, timezone -from todo.services.enhanced_dual_write_service import EnhancedDualWriteService - - -class Command(BaseCommand): - help = "Backfill user_roles so roles are present for every team member and fix incorrect assigned roles" - - def handle(self, *args, **options): - self.stdout.write(self.style.WARNING("\n--- Starting Team Roles Fix Script ---")) - teams_data = TeamRepository.get_collection().find({"is_deleted": False}) - roles_scope = RoleScope.TEAM.value - - roles_ensured = 0 - roles_deactivated = 0 - dual_write_service = EnhancedDualWriteService() - - roles_to_assign = [] - roles_to_remove = [] - postgres_operations = [] - - for team in teams_data: - team_id = str(team["_id"]) - team_owner = team["created_by"] - team_members = list(UserTeamDetailsRepository.get_by_team_id(team_id)) - for member in team_members: - member_id = str(member.user_id) - if member_id == team_owner: - member_roles = UserRoleRepository.get_user_roles(member_id, roles_scope, team_id) - existing_roles = [role.role_name for role in member_roles] - for role in (RoleName.OWNER.value, RoleName.ADMIN.value, RoleName.MEMBER.value): - if role not in existing_roles: - roles_to_assign.append( - { - "user_id": member_id, - "role_name": role, - "scope": roles_scope, - "team_id": team_id, - "is_active": True, - "created_at": datetime.now(timezone.utc), - "created_by": "system", - } - ) - else: - member_roles = UserRoleRepository.get_user_roles(member_id, roles_scope, team_id) - has_member_role = False - for role in member_roles: - if role.role_name == RoleName.MEMBER.value: - has_member_role = True - else: - roles_to_remove.append( - { - "mongo_id": role.id, - "user_id": member_id, - "role_name": role.role_name, - "scope": roles_scope, - "team_id": role.team_id, - "is_active": role.is_active, - "created_by": role.created_by, - "created_at": role.created_at, - } - ) - if not has_member_role: - roles_to_assign.append( - { - "user_id": member_id, - "role_name": RoleName.MEMBER.value, - "scope": roles_scope, - "team_id": team_id, - "is_active": True, - "created_at": datetime.now(timezone.utc), - "created_by": "system", - } - ) - - if roles_to_assign: - result = UserRoleRepository.get_collection().insert_many(roles_to_assign) - roles_ensured = len(result.inserted_ids) - self.stdout.write(self.style.SUCCESS(f"Successfully inserted {roles_ensured} new roles.")) - - for role_data, mongo_id in zip(roles_to_assign, result.inserted_ids): - postgres_operations.append( - { - "operation": "create", - "collection_name": "user_roles", - "mongo_id": str(mongo_id), - "data": role_data, - } - ) - - if roles_to_remove: - for role in roles_to_remove: - mongo_id = str(role["mongo_id"]) - postgres_operations.append( - { - "operation": "update", - "collection_name": "user_roles", - "mongo_id": mongo_id, - "data": { - "user_id": role["user_id"], - "role_name": role["role_name"], - "scope": role["scope"], - "team_id": role["team_id"], - "is_active": False, - "created_at": role["created_at"], - "created_by": role["created_by"], - }, - } - ) - role_ids_to_remove = [roles["mongo_id"] for roles in roles_to_remove] - result = UserRoleRepository.get_collection().update_many( - {"_id": {"$in": role_ids_to_remove}}, {"$set": {"is_active": False}} - ) - roles_deactivated = result.modified_count - self.stdout.write(self.style.SUCCESS(f"Successfully deactivated {roles_deactivated} roles.")) - if postgres_operations: - self.stdout.write( - self.style.WARNING(f"\nStarting sync of {len(postgres_operations)} operations to PostgreSQL...") - ) - success = dual_write_service.batch_operations(postgres_operations) - if success: - self.stdout.write(self.style.SUCCESS("PostgreSQL sync completed successfully.")) - else: - self.stdout.write(self.style.ERROR("PostgreSQL sync encountered errors. Check logs for details.")) diff --git a/todo/migrations/0004_backfill_user_role_data.py b/todo/migrations/0004_backfill_user_role_data.py new file mode 100644 index 00000000..fae88296 --- /dev/null +++ b/todo/migrations/0004_backfill_user_role_data.py @@ -0,0 +1,138 @@ +# Generated by Django 5.1.5 on 2025-09-23 07:25 + +from django.db import migrations +import logging +import sys +from todo.repositories.user_role_repository import UserRoleRepository +from todo.repositories.team_repository import TeamRepository, UserTeamDetailsRepository +from todo.constants.role import RoleScope, RoleName +from datetime import datetime, timezone +from todo.services.enhanced_dual_write_service import EnhancedDualWriteService + +logger = logging.getLogger(__name__) + + +def fix_team_roles(apps, schema_editor): + logger.info("\n--- Starting Team Roles Fix Script ---") + if 'test' in sys.argv: + logger.info("\n--- Skipping Team Roles Fix Script in test environment ---") + return + teams_data = TeamRepository.get_collection().find({"is_deleted": False}) + roles_scope = RoleScope.TEAM.value + + roles_ensured = 0 + roles_deactivated = 0 + dual_write_service = EnhancedDualWriteService() + + roles_to_assign = [] + roles_to_remove = [] + postgres_operations = [] + + for team in teams_data: + team_id = str(team["_id"]) + team_owner = team["created_by"] + team_members = list(UserTeamDetailsRepository.get_by_team_id(team_id)) + for member in team_members: + member_id = str(member.user_id) + if member_id == team_owner: + member_roles = UserRoleRepository.get_user_roles(member_id, roles_scope, team_id) + existing_roles = [role.role_name for role in member_roles] + for role in (RoleName.OWNER.value, RoleName.ADMIN.value, RoleName.MEMBER.value): + if role not in existing_roles: + roles_to_assign.append( + { + "user_id": member_id, + "role_name": role, + "scope": roles_scope, + "team_id": team_id, + "is_active": True, + "created_at": datetime.now(timezone.utc), + "created_by": "system", + } + ) + else: + member_roles = UserRoleRepository.get_user_roles(member_id, roles_scope, team_id) + has_member_role = False + for role in member_roles: + if role.role_name == RoleName.MEMBER.value: + has_member_role = True + else: + roles_to_remove.append( + { + "mongo_id": role.id, + "user_id": member_id, + "role_name": role.role_name, + "scope": roles_scope, + "team_id": role.team_id, + "is_active": role.is_active, + "created_by": role.created_by, + "created_at": role.created_at, + } + ) + if not has_member_role: + roles_to_assign.append( + { + "user_id": member_id, + "role_name": RoleName.MEMBER.value, + "scope": roles_scope, + "team_id": team_id, + "is_active": True, + "created_at": datetime.now(timezone.utc), + "created_by": "system", + } + ) + if roles_to_assign: + result = UserRoleRepository.get_collection().insert_many(roles_to_assign) + roles_ensured = len(result.inserted_ids) + logger.info(f"Successfully inserted {roles_ensured} new roles.") + + for role_data, mongo_id in zip(roles_to_assign, result.inserted_ids): + postgres_operations.append( + { + "operation": "create", + "collection_name": "user_roles", + "mongo_id": str(mongo_id), + "data": role_data, + } + ) + + if roles_to_remove: + for role in roles_to_remove: + mongo_id = str(role["mongo_id"]) + postgres_operations.append( + { + "operation": "update", + "collection_name": "user_roles", + "mongo_id": mongo_id, + "data": { + "user_id": role["user_id"], + "role_name": role["role_name"], + "scope": role["scope"], + "team_id": role["team_id"], + "is_active": False, + "created_at": role["created_at"], + "created_by": role["created_by"], + }, + } + ) + role_ids_to_remove = [roles["mongo_id"] for roles in roles_to_remove] + result = UserRoleRepository.get_collection().update_many( + {"_id": {"$in": role_ids_to_remove}}, {"$set": {"is_active": False}} + ) + roles_deactivated = result.modified_count + logger.info(f"Successfully deactivated {roles_deactivated} roles.") + if postgres_operations: + logger.info(f"\nStarting sync of {len(postgres_operations)} operations to PostgreSQL...") + success = dual_write_service.batch_operations(postgres_operations) + if success: + logger.info("PostgreSQL sync completed successfully.") + else: + logger.warning("PostgreSQL sync encountered errors. Check logs for details.") + + +class Migration(migrations.Migration): + dependencies = [ + ("todo", "0003_alter_postgresuserrole_unique_together_and_more"), + ] + + operations = [migrations.RunPython(fix_team_roles, migrations.RunPython.noop)] From 8e832e8dca79e82f51a7d7c9102e264141fc9789 Mon Sep 17 00:00:00 2001 From: Hariom Vashista Date: Tue, 23 Sep 2025 14:40:52 +0530 Subject: [PATCH 6/7] fix(user-roles): fix formatting issues --- todo/migrations/0004_backfill_user_role_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/todo/migrations/0004_backfill_user_role_data.py b/todo/migrations/0004_backfill_user_role_data.py index fae88296..07931dab 100644 --- a/todo/migrations/0004_backfill_user_role_data.py +++ b/todo/migrations/0004_backfill_user_role_data.py @@ -14,7 +14,7 @@ def fix_team_roles(apps, schema_editor): logger.info("\n--- Starting Team Roles Fix Script ---") - if 'test' in sys.argv: + if "test" in sys.argv: logger.info("\n--- Skipping Team Roles Fix Script in test environment ---") return teams_data = TeamRepository.get_collection().find({"is_deleted": False}) From 1c74e4afefae06ad16020899d1d53a741c84b236 Mon Sep 17 00:00:00 2001 From: Hariom Vashista Date: Tue, 23 Sep 2025 14:50:02 +0530 Subject: [PATCH 7/7] fix(user-roles): move bulk operation out of for loop --- todo/migrations/0004_backfill_user_role_data.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/todo/migrations/0004_backfill_user_role_data.py b/todo/migrations/0004_backfill_user_role_data.py index 07931dab..05edb770 100644 --- a/todo/migrations/0004_backfill_user_role_data.py +++ b/todo/migrations/0004_backfill_user_role_data.py @@ -115,12 +115,12 @@ def fix_team_roles(apps, schema_editor): }, } ) - role_ids_to_remove = [roles["mongo_id"] for roles in roles_to_remove] - result = UserRoleRepository.get_collection().update_many( - {"_id": {"$in": role_ids_to_remove}}, {"$set": {"is_active": False}} - ) - roles_deactivated = result.modified_count - logger.info(f"Successfully deactivated {roles_deactivated} roles.") + role_ids_to_remove = [roles["mongo_id"] for roles in roles_to_remove] + result = UserRoleRepository.get_collection().update_many( + {"_id": {"$in": role_ids_to_remove}}, {"$set": {"is_active": False}} + ) + roles_deactivated = result.modified_count + logger.info(f"Successfully deactivated {roles_deactivated} roles.") if postgres_operations: logger.info(f"\nStarting sync of {len(postgres_operations)} operations to PostgreSQL...") success = dual_write_service.batch_operations(postgres_operations)