Fix malformed user uuids to fix automations [automations data loss]

- **Malformed automations will be dropped**
  They can't run with malformed user uuid anyway.
This commit is contained in:
Debanjum
2025-06-03 19:34:50 -07:00
parent 4892e73323
commit d45d9d4cfb
2 changed files with 107 additions and 1 deletions

View File

@@ -0,0 +1,106 @@
# Generated by Django 5.1.9 on 2025-06-04 01:11
import uuid
from django.db import migrations, models
def fix_malformed_uuids(apps, schema_editor):
KhojUser = apps.get_model("database", "KhojUser")
# Track UUID changes for automation cleanup
uuid_mappings = {}
# Handle null or empty user UUIDs
for user in KhojUser.objects.filter(uuid__isnull=True):
old_uuid = str(user.uuid) if user.uuid else "None"
user.uuid = uuid.uuid4()
user.save()
uuid_mappings[old_uuid] = str(user.uuid)
# Handle malformed user UUIDs
for user in KhojUser.objects.all():
current_uuid_val = user.uuid
try:
if not isinstance(current_uuid_val, uuid.UUID):
# Attempt to parse it as UUID. This will catch "None", "null" strings or other malformed hex.
uuid.UUID(str(current_uuid_val))
except (ValueError, TypeError, AttributeError):
old_uuid_str = str(current_uuid_val)
new_uuid_obj = uuid.uuid4()
user.uuid = new_uuid_obj
user.save(
update_fields=["uuid"]
) # Important to use update_fields to avoid triggering full save logic if not needed
uuid_mappings[old_uuid_str] = str(new_uuid_obj)
print(f"Fixed malformed UUID for user (old: '{old_uuid_str}', new: {str(new_uuid_obj)})")
# Clean up orphaned automations
cleanup_orphaned_automations(uuid_mappings)
def cleanup_orphaned_automations(uuid_mappings):
"""Remove automations with malformed UUIDs in job_ids"""
from apscheduler.jobstores.base import JobLookupError
from khoj.utils import state
if not state.scheduler:
return
all_jobs = state.scheduler.get_jobs()
removed_orphaned_count = 0
removed_malformed_count = 0
for job in all_jobs:
if job.id.startswith("automation_"):
# Extract UUID from job_id: "automation_{uuid}_{query_id}"
job_parts = job.id.split("_", 2)
if len(job_parts) >= 2:
job_uuid = job_parts[1]
# Check if this UUID was malformed
if job_uuid in uuid_mappings:
# Remove orphaned automation
try:
state.scheduler.remove_job(job.id)
removed_orphaned_count += 1
print(f"Removed orphaned automation: {job.id}")
except JobLookupError:
pass # Job already removed
# Also remove jobs with clearly malformed UUIDs
elif job_uuid in ["None", "null"] or not is_valid_uuid(job_uuid):
try:
state.scheduler.remove_job(job.id)
removed_malformed_count += 1
print(f"Removed automation with malformed UUID: {job.id}")
except JobLookupError:
pass
if removed_orphaned_count > 0 or removed_malformed_count > 0:
print(f"Removed {removed_orphaned_count} orphaned and {removed_malformed_count} malformed automations.")
def is_valid_uuid(uuid_string):
"""Check if string is a valid UUID"""
try:
uuid.UUID(str(uuid_string))
return True
except (ValueError, TypeError, AttributeError):
return False
class Migration(migrations.Migration):
dependencies = [
("database", "0089_chatmodel_price_tier_and_more"),
]
operations = [
migrations.RunPython(fix_malformed_uuids, reverse_code=migrations.RunPython.noop),
migrations.AlterField(
model_name="khojuser",
name="uuid",
field=models.UUIDField(default=uuid.uuid4, editable=False, unique=True),
),
]

View File

@@ -137,7 +137,7 @@ class ClientApplication(DbBaseModel):
class KhojUser(AbstractUser):
uuid = models.UUIDField(models.UUIDField(default=uuid.uuid4, editable=False))
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
phone_number = PhoneNumberField(null=True, default=None, blank=True)
verified_phone_number = models.BooleanField(default=False)
verified_email = models.BooleanField(default=False)