#!/usr/bin/env python3 """ Upload local profile pictures to Supabase and update user/profile records. Usage: python scripts/update_profile_pictures.py --dir profile_pictures --apply python scripts/update_profile_pictures.py --dir profile_pictures # dry run """ import argparse import asyncio import mimetypes import sys from pathlib import Path ROOT = Path(__file__).resolve().parents[1] if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) from loguru import logger from src.database import db from src.services.storage_service import storage def guess_content_type(path: Path) -> str: content_type, _ = mimetypes.guess_type(str(path)) return content_type or "image/jpeg" async def process_directory(directory: Path, apply: bool) -> None: if not directory.exists() or not directory.is_dir(): raise ValueError(f"Directory not found: {directory}") files = sorted([p for p in directory.iterdir() if p.is_file()]) if not files: print("No files found.") return updated = 0 skipped = 0 for path in files: email = path.stem.strip() if "@" not in email: print(f"Skip (no email in filename): {path.name}") skipped += 1 continue user = await db.get_user_by_email(email) if not user: print(f"User not found for email: {email}") skipped += 1 continue content_type = guess_content_type(path) file_bytes = path.read_bytes() print(f"\n{path.name} -> {email} ({content_type}, {len(file_bytes)} bytes)") if not apply: print("DRY RUN: would upload and update profile.") continue try: uploaded_url = await storage.upload_media( file_content=file_bytes, content_type=content_type, user_id=str(user.id), ) await db.update_profile(user.id, {"profile_picture": uploaded_url}) linkedin_account = await db.get_linkedin_account(user.id) if linkedin_account: await db.update_linkedin_account( linkedin_account.id, {"linkedin_picture": uploaded_url} ) if db.admin_client: try: await asyncio.to_thread( lambda: db.admin_client.auth.admin.update_user_by_id( str(user.id), { "user_metadata": { "picture": uploaded_url, "linkedin_picture": uploaded_url } } ) ) except Exception as exc: logger.warning(f"Failed to update auth user metadata: {exc}") else: logger.warning("No service role key available; cannot update auth.users metadata.") updated += 1 print(f"Updated profile picture: {uploaded_url}") except Exception as exc: logger.error(f"Failed to update {email}: {exc}") skipped += 1 print(f"\nDone. Updated: {updated}, Skipped: {skipped}") def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description="Upload profile pictures to Supabase and update users.") parser.add_argument("--dir", default="profile_pictures", help="Directory with profile pictures") parser.add_argument("--apply", action="store_true", help="Apply changes (otherwise dry run)") return parser.parse_args() if __name__ == "__main__": args = parse_args() asyncio.run(process_directory(Path(args.dir), apply=args.apply))