AdarshDRC commited on
Commit
6c6ffa6
·
1 Parent(s): 572243e

fix : Lazy Loading issues

Browse files
Files changed (1) hide show
  1. main.py +76 -45
main.py CHANGED
@@ -169,8 +169,6 @@ DEFAULT_PC_KEY = os.getenv("DEFAULT_PINECONE_KEY", "")
169
  DEFAULT_CLD_URL = os.getenv("DEFAULT_CLOUDINARY_URL", "")
170
 
171
  def _is_default_key(key: str, default: str) -> bool:
172
- if not key or not key.strip(): # empty key never matches — prevents blocking users with no keys
173
- return False
174
  return bool(default) and key.strip() == default.strip()
175
 
176
 
@@ -739,34 +737,72 @@ async def health():
739
  # ════════════════════════════════════════════════════════════════
740
  # 5. LIST FOLDER IMAGES
741
  # ════════════════════════════════════════════════════════════════
742
- def _cld_list_folder_images(folder: str, creds: dict, next_cursor: str = None):
743
- kwargs = dict(type="upload", prefix=f"{folder}/", max_results=500,
744
  api_key=creds["api_key"], api_secret=creds["api_secret"], cloud_name=creds["cloud_name"])
745
  if next_cursor: kwargs["next_cursor"] = next_cursor
746
  return cloudinary.api.resources(**kwargs)
747
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
748
  @app.post("/api/cloudinary/folder-images")
749
  async def list_folder_images(
750
  request: Request,
751
  user_cloudinary_url: str = Form(""),
752
  folder_name: str = Form(...),
753
  user_id: str = Form(""),
 
 
754
  ):
755
  ip = get_ip(request)
756
  actual_url = user_cloudinary_url or os.getenv("DEFAULT_CLOUDINARY_URL", "")
757
  creds = get_cloudinary_creds(actual_url)
758
  if not creds.get("cloud_name"): raise HTTPException(400, "Invalid Cloudinary URL.")
759
- images, next_cursor = [], None
760
- while True:
761
- result = await asyncio.to_thread(_cld_list_folder_images, folder_name, creds, next_cursor)
762
- for r in result.get("resources", []):
763
- images.append({"url": r["secure_url"], "public_id": r["public_id"]})
764
- next_cursor = result.get("next_cursor")
765
- if not next_cursor: break
 
 
 
 
 
 
 
 
 
 
766
  log("INFO", "explorer.folder_opened",
767
  user_id=user_id or "anonymous", ip=ip,
768
- folder_name=folder_name, image_count=len(images))
769
- return {"images": images, "count": len(images)}
 
770
 
771
 
772
  # ════════════════════════════════════════════════════════════════
@@ -993,39 +1029,34 @@ async def delete_account(
993
 
994
  creds = get_cloudinary_creds(user_cloudinary_url)
995
 
996
- # ── Cloudinary: skip gracefully if user has no credentials ───
997
- if creds.get("cloud_name"):
998
- try:
999
- deleted = await asyncio.to_thread(_cld_delete_all_paginated, creds)
1000
- _log_fn("INFO", f"Account delete Cloudinary: {deleted} resources removed")
1001
- except Exception as e:
1002
- _log_fn("WARNING", f"Account delete Cloudinary: {e}")
1003
- try:
1004
- folders_res = await asyncio.to_thread(_cld_root_folders, creds)
1005
- folder_tasks = [
1006
- asyncio.to_thread(_cld_remove_folder, f["name"], creds)
1007
- for f in folders_res.get("folders", [])
1008
- ]
1009
- if folder_tasks:
1010
- await asyncio.gather(*folder_tasks, return_exceptions=True)
1011
- except Exception as e:
1012
- _log_fn("WARNING", f"Account delete Cloudinary folders: {e}")
1013
- else:
1014
- _log_fn("INFO", "Account delete: no Cloudinary credentials — skipping CDN wipe")
1015
 
1016
- # ── Pinecone: skip gracefully if user has no key ─────────────
1017
- if user_pinecone_key and user_pinecone_key.strip():
1018
- try:
1019
- pc = _get_pinecone(user_pinecone_key)
1020
- existing = {idx.name for idx in await asyncio.to_thread(pc.list_indexes)}
1021
- tasks = []
1022
- if IDX_OBJECTS in existing: tasks.append(asyncio.to_thread(pc.delete_index, IDX_OBJECTS))
1023
- if IDX_FACES in existing: tasks.append(asyncio.to_thread(pc.delete_index, IDX_FACES))
1024
- if tasks: await asyncio.gather(*tasks)
1025
- except Exception as e:
1026
- _log_fn("WARNING", f"Account delete Pinecone: {e}")
1027
- else:
1028
- _log_fn("INFO", "Account delete: no Pinecone key — skipping vector wipe")
 
 
 
 
 
 
 
 
1029
 
1030
  log("WARNING", "danger.delete_account.complete",
1031
  user_id=user_id or "anonymous", ip=ip,
 
169
  DEFAULT_CLD_URL = os.getenv("DEFAULT_CLOUDINARY_URL", "")
170
 
171
  def _is_default_key(key: str, default: str) -> bool:
 
 
172
  return bool(default) and key.strip() == default.strip()
173
 
174
 
 
737
  # ════════════════════════════════════════════════════════════════
738
  # 5. LIST FOLDER IMAGES
739
  # ════════════════════════════════════════════════════════════════
740
+ def _cld_list_folder_images(folder: str, creds: dict, next_cursor: str = None, max_results: int = 100):
741
+ kwargs = dict(type="upload", prefix=f"{folder}/", max_results=max_results,
742
  api_key=creds["api_key"], api_secret=creds["api_secret"], cloud_name=creds["cloud_name"])
743
  if next_cursor: kwargs["next_cursor"] = next_cursor
744
  return cloudinary.api.resources(**kwargs)
745
 
746
+
747
+ def _cld_thumb_url(secure_url: str, cloud_name: str) -> str:
748
+ """
749
+ Convert full-resolution Cloudinary URL to a small thumbnail URL.
750
+ Inserts Cloudinary transformation: 400×400 fill, auto quality, auto format.
751
+ Example:
752
+ https://res.cloudinary.com/demo/image/upload/v123/folder/img.jpg
753
+
754
+ https://res.cloudinary.com/demo/image/upload/w_400,h_400,c_fill,q_auto,f_auto/v123/folder/img.jpg
755
+ """
756
+ try:
757
+ marker = f"/image/upload/"
758
+ idx = secure_url.find(marker)
759
+ if idx == -1:
760
+ return secure_url # can't transform — return original
761
+ base = secure_url[:idx + len(marker)]
762
+ rest = secure_url[idx + len(marker):]
763
+ # Skip existing transformation block if present
764
+ if rest.startswith("w_") or rest.startswith("h_") or rest.startswith("c_") or rest.startswith("q_"):
765
+ return secure_url # already transformed
766
+ return f"{base}w_400,h_400,c_fill,q_auto,f_auto/{rest}"
767
+ except Exception:
768
+ return secure_url
769
+
770
+
771
  @app.post("/api/cloudinary/folder-images")
772
  async def list_folder_images(
773
  request: Request,
774
  user_cloudinary_url: str = Form(""),
775
  folder_name: str = Form(...),
776
  user_id: str = Form(""),
777
+ next_cursor: str = Form(""),
778
+ page_size: int = Form(100),
779
  ):
780
  ip = get_ip(request)
781
  actual_url = user_cloudinary_url or os.getenv("DEFAULT_CLOUDINARY_URL", "")
782
  creds = get_cloudinary_creds(actual_url)
783
  if not creds.get("cloud_name"): raise HTTPException(400, "Invalid Cloudinary URL.")
784
+
785
+ # Single page fetch — frontend drives pagination via next_cursor
786
+ result = await asyncio.to_thread(
787
+ _cld_list_folder_images, folder_name, creds,
788
+ next_cursor or None, min(page_size, 100)
789
+ )
790
+ images = []
791
+ for r in result.get("resources", []):
792
+ full_url = r["secure_url"]
793
+ thumb_url = _cld_thumb_url(full_url, creds["cloud_name"])
794
+ images.append({
795
+ "url": full_url, # full-res for lightbox / download
796
+ "thumb_url": thumb_url, # 400×400 thumbnail for grid display
797
+ "public_id": r["public_id"],
798
+ })
799
+
800
+ response_cursor = result.get("next_cursor") or ""
801
  log("INFO", "explorer.folder_opened",
802
  user_id=user_id or "anonymous", ip=ip,
803
+ folder_name=folder_name, image_count=len(images),
804
+ has_more=bool(response_cursor))
805
+ return {"images": images, "count": len(images), "next_cursor": response_cursor}
806
 
807
 
808
  # ════════════════════════════════════════════════════════════════
 
1029
 
1030
  creds = get_cloudinary_creds(user_cloudinary_url)
1031
 
1032
+ # ── Cloudinary: paginated delete ALL resources then folders ───
1033
+ try:
1034
+ deleted = await asyncio.to_thread(_cld_delete_all_paginated, creds)
1035
+ _log_fn("INFO", f"Account delete Cloudinary: {deleted} resources removed")
1036
+ except Exception as e:
1037
+ _log_fn("WARNING", f"Account delete Cloudinary: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
1038
 
1039
+ try:
1040
+ folders_res = await asyncio.to_thread(_cld_root_folders, creds)
1041
+ folder_tasks = [
1042
+ asyncio.to_thread(_cld_remove_folder, f["name"], creds)
1043
+ for f in folders_res.get("folders", [])
1044
+ ]
1045
+ if folder_tasks:
1046
+ await asyncio.gather(*folder_tasks, return_exceptions=True)
1047
+ except Exception as e:
1048
+ _log_fn("WARNING", f"Account delete Cloudinary folders: {e}")
1049
+
1050
+ # ── Pinecone: delete both indexes ────────────────────────────
1051
+ try:
1052
+ pc = _get_pinecone(user_pinecone_key)
1053
+ existing = {idx.name for idx in await asyncio.to_thread(pc.list_indexes)}
1054
+ tasks = []
1055
+ if IDX_OBJECTS in existing: tasks.append(asyncio.to_thread(pc.delete_index, IDX_OBJECTS))
1056
+ if IDX_FACES in existing: tasks.append(asyncio.to_thread(pc.delete_index, IDX_FACES))
1057
+ if tasks: await asyncio.gather(*tasks)
1058
+ except Exception as e:
1059
+ _log_fn("WARNING", f"Account delete Pinecone: {e}")
1060
 
1061
  log("WARNING", "danger.delete_account.complete",
1062
  user_id=user_id or "anonymous", ip=ip,