14 Migrate existing project owners from projects.id_users to project_members table.
15 This function is idempotent - it can be run multiple times safely.
18 logging.info(
"Starting project owners migration...")
21 projects_without_members = db.session.execute(text(
"""
22 SELECT p.id, p.id_users, p.name
24 LEFT JOIN project_members pm ON p.id = pm.project_id AND pm.role_type = 'owner'
25 WHERE p.id_users IS NOT NULL
26 AND pm.project_id IS NULL
30 projects_without_owners = db.session.execute(text(
"""
33 LEFT JOIN project_members pm ON p.id = pm.project_id AND pm.role_type = 'owner'
34 WHERE p.id_users IS NULL
35 AND pm.project_id IS NULL
38 total_projects_to_migrate = len(projects_without_members) + len(projects_without_owners)
40 if total_projects_to_migrate == 0:
41 logging.info(
"No projects need migration - all owners already migrated")
44 logging.info(f
"Found {len(projects_without_members)} projects with id_users to migrate")
45 logging.info(f
"Found {len(projects_without_owners)} projects without any owner")
46 logging.info(f
"Total projects to process: {total_projects_to_migrate}")
49 orphaned_projects = []
52 for project_id, owner_id, project_name
in projects_without_members:
55 owner_user = db.session.query(models.Users).filter_by(id=owner_id).first()
57 logging.warning(f
"Owner user {owner_id} not found for project {project_name} (ID: {project_id})")
58 orphaned_projects.append((project_id, project_name, f
"User {owner_id} not found"))
62 owner_membership = models.ProjectMembers(
63 project_id=project_id,
67 id_projects=project_id
70 db.session.add(owner_membership)
73 logging.info(f
"Created owner membership for project '{project_name}' (ID: {project_id}) -> User '{owner_user.name}' (ID: {owner_id})")
75 except Exception
as e:
76 logging.error(f
"Error migrating project {project_id}: {str(e)}")
77 orphaned_projects.append((project_id, project_name, str(e)))
81 for project_id, project_name
in projects_without_owners:
84 first_admin = db.session.query(models.Users).filter_by(is_admin=
True).first()
88 owner_membership = models.ProjectMembers(
89 project_id=project_id,
90 user_id=first_admin.id,
92 id_users=first_admin.id,
93 id_projects=project_id
96 db.session.add(owner_membership)
100 project = db.session.query(models.Projects).filter_by(id=project_id).first()
102 project.id_users = first_admin.id
104 logging.info(f
"Assigned admin '{first_admin.name}' as owner for orphaned project '{project_name}' (ID: {project_id})")
107 orphaned_projects.append((project_id, project_name,
"No admin user available"))
108 logging.warning(f
"No admin user found to assign as owner for project '{project_name}' (ID: {project_id})")
110 except Exception
as e:
111 logging.error(f
"Error handling orphaned project {project_id}: {str(e)}")
112 orphaned_projects.append((project_id, project_name, str(e)))
117 logging.info(f
"Successfully migrated {migrated_count} project owners")
120 if orphaned_projects:
121 logging.warning(f
"Found {len(orphaned_projects)} orphaned projects that could not be migrated:")
122 for project_id, project_name, reason
in orphaned_projects:
123 logging.warning(f
" - Project '{project_name}' (ID: {project_id}): {reason}")
124 logging.warning(
"These projects need manual intervention via the admin interface")
128 except Exception
as e:
129 logging.error(f
"Migration failed: {str(e)}")
130 db.session.rollback()
198 Get list of projects that don't have any owner
202 projects_with_invalid_owner = db.session.execute(text(
"""
203 SELECT p.id, p.name, p.id_users
205 LEFT JOIN users u ON p.id_users = u.id
206 WHERE p.id_users IS NOT NULL
211 projects_without_owner = db.session.execute(text(
"""
214 LEFT JOIN project_members pm ON p.id = pm.project_id AND pm.role_type = 'owner'
215 WHERE p.id_users IS NULL
216 AND pm.project_id IS NULL
220 'invalid_owner': projects_with_invalid_owner,
221 'no_owner': projects_without_owner,
222 'total_orphaned': len(projects_with_invalid_owner) + len(projects_without_owner)
225 except Exception
as e:
226 logging.error(f
"Error getting orphaned projects: {str(e)}")
232 Validate that migration was successful by checking data consistency
236 orphaned_members = db.session.execute(text(
"""
238 FROM project_members pm
239 LEFT JOIN projects p ON pm.project_id = p.id
244 projects_without_owner_membership = db.session.execute(text(
"""
247 LEFT JOIN project_members pm ON p.id = pm.project_id AND pm.role_type = 'owner'
248 WHERE p.id_users IS NOT NULL
249 AND pm.project_id IS NULL
253 multiple_owners = db.session.execute(text(
"""
254 SELECT project_id, COUNT(*)
256 WHERE role_type = 'owner'
261 validation_result = {
262 'orphaned_members': orphaned_members,
263 'projects_without_owner_membership': projects_without_owner_membership,
264 'projects_with_multiple_owners': len(multiple_owners),
265 'multiple_owners_details': multiple_owners,
266 'is_valid': orphaned_members == 0
and projects_without_owner_membership == 0
and len(multiple_owners) == 0
269 if validation_result[
'is_valid']:
270 logging.info(
"Migration validation passed")
272 logging.warning(f
"Migration validation issues: {validation_result}")
274 return validation_result
276 except Exception
as e:
277 logging.error(f
"Migration validation failed: {str(e)}")