Skip to content

Conversation

@Hariom01010
Copy link
Contributor

@Hariom01010 Hariom01010 commented Sep 22, 2025

Date: 22 Sep 2025

Developer Name: @Hariom01010


Issue Ticket Number

Closes #278

Description

A migration script is written to backfill user role data for teams.

  • For team owner, it adds the owner, admin and member role (if it doesn't exist).
  • For normal members, it only adds member role (if it doesn't exist). If any other role exists for normal members, it deactivates them.

To run the script, connect to the backend server and enter the following command:

python manage.py migrate_add_roles_to_teams
  1. The above script fetches all teams data.
  2. For each member of the team check if they are the owner or not.
  3. If they are owner assign the owner, admin and member role
  4. If they are not the owner assign the member role.

Documentation Updated?

  • Yes
  • No

Under Feature Flag

  • Yes
  • No

Database Changes

  • Yes
  • No

Breaking Changes

  • Yes
  • No

Development Tested?

  • Yes
  • No

Screenshots

Screenshot 1

Before running script for a team whose member don't have roles:

image image

After running script:

image image image

Test Coverage

Screenshot 1 image

Additional Notes

Description by Korbit AI

What change is being made?

Add a new Django migration (0004_backfill_user_role_data.py) that backfills and synchronizes user role data across team members by ensuring proper role assignments (OWNER/ADMIN/MEMBER) for team owners, ensuring MEMBER role for members, deactivating non-MEMBER roles, and syncing changes to PostgreSQL via a dual-write service.

Why are these changes being made?

Restore and enforce consistent user role data across teams, and keep MongoDB and PostgreSQL representations in sync during backfill. The script is skipped in test environments to avoid interference.

Is this description stale? Ask me to generate a new description by commenting /korbit-generate-pr-description

@Hariom01010 Hariom01010 self-assigned this Sep 22, 2025
@coderabbitai
Copy link

coderabbitai bot commented Sep 22, 2025

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Summary by CodeRabbit

  • Chores
    • Added a management command to standardize team role assignments across all teams.
    • Ensures owners receive appropriate roles and members have the correct default role.
    • Deactivates roles that don’t match expected team-level permissions.
    • Provides a summary of ensured and deactivated roles after execution.
    • No user-facing changes.

Walkthrough

Adds a Django management command that iterates all teams to backfill and correct user roles per team. It assigns OWNER/ADMIN/MEMBER to the team owner (excluding MODERATOR), ensures only MEMBER for other members, deactivates incorrect roles, and outputs counters for ensured and deactivated roles.

Changes

Cohort / File(s) Summary
Management command: backfill team roles
todo/management/commands/migrate_add_roles_to_teams.py
New Django command that scans teams, assigns correct roles to owners and members, removes invalid roles, and reports counts. Uses UserRoleRepository, TeamRepository, UserTeamDetailsRepository, RoleScope, and RoleName.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor Operator as CLI Operator
    participant Django as Django mgmt command
    participant TeamRepo as TeamRepository
    participant UTDRepo as UserTeamDetailsRepository
    participant RoleRepo as UserRoleRepository

    Operator->>Django: python manage.py migrate_add_roles_to_teams
    Django->>TeamRepo: list_all_teams()
    TeamRepo-->>Django: teams

    loop for each team
        Django->>UTDRepo: get_team_owner(team)
        UTDRepo-->>Django: owner
        rect rgba(200,240,255,0.3)
        note right of Django: Ensure owner roles (OWNER, ADMIN, MEMBER)<br/>Exclude MODERATOR
        Django->>RoleRepo: ensure_role(owner, team, OWNER/ADMIN/MEMBER)
        end

        Django->>UTDRepo: list_team_members(team)
        UTDRepo-->>Django: members
        loop for each member
            alt member is owner
                note right of Django: Already handled
            else other member
                Django->>RoleRepo: get_roles(member, team)
                RoleRepo-->>Django: roles
                rect rgba(230,255,230,0.4)
                note right of Django: Deactivate any non-MEMBER roles<br/>Ensure MEMBER exists
                Django->>RoleRepo: deactivate_roles(!= MEMBER)
                Django->>RoleRepo: ensure_role(member, team, MEMBER)
                end
            end
        end
    end

    Django-->>Operator: Print roles_ensured and roles_deactivated totals
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • Team member fix #157 — Adds a Django management command for backfilling team-related user records and uses UserTeamDetailsRepository, overlapping in domain and data touchpoints.

Poem

A rabbit taps keys with a careful hop,
Sorting roles in every team shop.
Owners get their triple crown,
Members keep it simple, toned down.
Counters tick—ensure, deactivate—
Carrots tallied, data straight. 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Linked Issues Check ⚠️ Warning The implementation appears to meet the linked issue's goals for normal members by ensuring MEMBER role existence and deactivating incorrect roles, but the provided summary indicates owners may be granted "all roles except MODERATOR," which conflicts with the linked issue and PR objective that owners should only receive OWNER, ADMIN, and MEMBER roles; this inconsistency prevents confirming full compliance without inspecting the command's code to verify which specific roles are assigned to owners. The script's reporting of created and deactivated role counts and its handling of non-owner members indicate partial alignment with the issue requirements. Full compliance cannot be certified until the owner-role assignment behavior is clarified or corrected. Update the command to restrict owner role assignments to OWNER, ADMIN, and MEMBER (not a broad "all roles" assignment) and add or update unit/integration tests and code comments to show the exact roles applied; alternatively, if broader assignment is intentional, update the PR and linked issue text to document and justify the deviation so reviewers can verify compliance.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The PR title "fix/user-roles: add migration script to backfill user role details" succinctly and accurately reflects the primary change — adding a migration/management command to backfill user role details. It is concise, includes the fix scope, and is specific enough for a reviewer to understand the main intent. The title avoids noise and matches the changes described in the diff summary.
Out of Scope Changes Check ✅ Passed According to the provided summary the changes are limited to a single new management command file (migrate_add_roles_to_teams.py) and there are no other modified files or unrelated logic introduced, so no out-of-scope changes were detected. The PR appears focused on the stated objective of backfilling and correcting team user roles.
Description Check ✅ Passed The PR description clearly documents the migration command, its intended behavior, usage instructions, the linked issue it closes, and includes screenshots and test evidence, so it is directly related to the changeset. It provides sufficient context for reviewers to understand the intent and how to run the script. The description is not vague or off-topic.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Hariom01010 Hariom01010 added the bug Something isn't working label Sep 22, 2025
Copy link

@korbit-ai korbit-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review by Korbit AI

Korbit automatically attempts to detect when you fix issues in new commits.
Category Issue Status
Performance Inefficient individual role processing ▹ view ✅ Fix detected
Functionality Inaccurate role assignment counter ▹ view ✅ Fix detected
Performance N+1 database query pattern ▹ view ✅ Fix detected
Functionality Unchecked MEMBER role assignment result ▹ view ✅ Fix detected
Readability Negative condition complexity ▹ view ✅ Fix detected
Documentation Insufficient context in command help message ▹ view ✅ Fix detected
Design Inflexible role assignment logic ▹ view ✅ Fix detected
Security Excessive role assignment to team owners ▹ view ✅ Fix detected
Logging Insufficient intermediate logging ▹ view ✅ Fix detected
Files scanned
File Path Reviewed
todo/management/commands/migrate_add_roles_to_teams.py

Explore our documentation to understand the languages and file types we support and the files we ignore.

Check out our docs on how you can make Korbit work best for you and your team.

Loving Korbit!? Share us on LinkedIn Reddit and X

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
todo/management/commands/migrate_add_roles_to_teams.py (1)

1-44: Repo/methods present — fix enum/string usage, duplicate class, and owner-type compare before prod

  • Confirmed: UserRoleRepository and methods exist (todo/repositories/user_role_repository.py — get_user_roles @67, assign_role @113, remove_role_by_id @121) and TeamRepository + UserTeamDetailsRepository are defined in todo/repositories/team_repository.py (class @126, get_by_team_id @262).
  • Duplicate class: UserTeamDetailsRepository is also defined in todo/repositories/user_team_details_repository.py (@6) — ensure the import from team_repository is intentional.
  • Type mismatch: migrate_add_roles_to_teams.py passes RoleName.value and RoleScope.TEAM.value (strings) to repo methods that are type-hinted for RoleName/RoleScope (see user_role_repository.py @113). Pass enums (RoleName/RoleScope) or make the repo accept strings. (migrate_add_roles_to_teams.py calls at ~lines 24–26 and 37–40.)
  • Owner comparison: the script uses team["created_by"] compared to str(member.user_id); created_by is a PyObjectId in todo/models/team.py (@31). Normalize types (e.g., str(team["created_by"])) before comparing.

Fix the above before running in production.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6f01590 and 6671934.

📒 Files selected for processing (1)
  • todo/management/commands/migrate_add_roles_to_teams.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
todo/management/commands/migrate_add_roles_to_teams.py (3)
todo/repositories/user_role_repository.py (1)
  • UserRoleRepository (14-165)
todo/repositories/team_repository.py (1)
  • TeamRepository (11-123)
todo/constants/role.py (2)
  • RoleScope (4-6)
  • RoleName (9-13)

Comment on lines 72 to 115
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."))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Data consistency risk: MongoDB operations complete first (lines 73-108), then PostgreSQL sync happens (line 111). If the PostgreSQL sync fails (success = False), the databases will be in an inconsistent state with no rollback mechanism. This violates data integrity in a dual-write system. Consider implementing transaction-like behavior with rollback capability, or at minimum, fail the entire operation if PostgreSQL sync fails instead of just logging an error.

Suggested change
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."))
if roles_to_assign:
# Store MongoDB operations to allow rollback if PostgreSQL sync fails
mongo_operations_performed = False
inserted_ids = []
try:
result = UserRoleRepository.get_collection().insert_many(roles_to_assign)
inserted_ids = result.inserted_ids
roles_ensured = len(inserted_ids)
mongo_operations_performed = True
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
})
except Exception as e:
self.stdout.write(self.style.ERROR(f"Failed to insert roles in MongoDB: {str(e)}"))
raise
if roles_to_remove:
role_ids_to_remove = []
try:
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 = [role["mongo_id"] for role 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."))
except Exception as e:
self.stdout.write(self.style.ERROR(f"Failed to deactivate roles in MongoDB: {str(e)}"))
raise
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:
# Attempt to rollback MongoDB changes if PostgreSQL sync fails
if mongo_operations_performed and inserted_ids:
try:
self.stdout.write(self.style.WARNING("PostgreSQL sync failed. Attempting to rollback MongoDB changes..."))
UserRoleRepository.get_collection().delete_many({"_id": {"$in": inserted_ids}})
self.stdout.write(self.style.SUCCESS("MongoDB rollback completed."))
except Exception as rollback_error:
self.stdout.write(self.style.ERROR(f"MongoDB rollback failed: {str(rollback_error)}"))
if role_ids_to_remove:
try:
UserRoleRepository.get_collection().update_many(
{"_id": {"$in": role_ids_to_remove}},
{"$set": {"is_active": True}}
)
self.stdout.write(self.style.SUCCESS("MongoDB role deactivation rollback completed."))
except Exception as rollback_error:
self.stdout.write(self.style.ERROR(f"MongoDB role deactivation rollback failed: {str(rollback_error)}"))
self.stdout.write(self.style.ERROR("PostgreSQL sync failed. MongoDB changes have been rolled back."))
raise CommandError("Failed to sync changes to PostgreSQL. Operation aborted.")

Spotted by Diamond

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

},
}
)
role_ids_to_remove = [roles["mongo_id"] for roles in roles_to_remove]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable name in the list comprehension doesn't match the iteration variable. The line should be:

role_ids_to_remove = [role["mongo_id"] for role in roles_to_remove]

Currently it uses roles as the iteration variable but then tries to access roles["mongo_id"], which would cause an error since roles is a single dictionary from the list, not the entire list itself.

Suggested change
role_ids_to_remove = [roles["mongo_id"] for roles in roles_to_remove]
role_ids_to_remove = [role["mongo_id"] for role in roles_to_remove]

Spotted by Diamond

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

@iamitprakash iamitprakash merged commit 0c0129c into RealDevSquad:develop Sep 23, 2025
3 checks passed
iamitprakash added a commit that referenced this pull request Sep 23, 2025
Merge pull request #279 from Hariom01010/fix/backfill-user-roles
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fix user role data

2 participants