From da4e2932bd1e9b40785cedccd8560f462185f127 Mon Sep 17 00:00:00 2001 From: kokialgo Date: Mon, 13 Oct 2025 13:39:12 -0300 Subject: [PATCH 1/4] feat: script to delete photo and video from pohId on user request --- delete_poh_user_data.py | 172 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 delete_poh_user_data.py diff --git a/delete_poh_user_data.py b/delete_poh_user_data.py new file mode 100644 index 0000000..cdba520 --- /dev/null +++ b/delete_poh_user_data.py @@ -0,0 +1,172 @@ +""" +Script to delete PoH user data (profile picture and video) from Filebase using their profile ID. +""" +from logging import Logger +import sys +import requests +from filebase_datatypes import GetPinsResponse +from filebase_pin_api import FilebasePinAPI +from logger import setup_logger + +# GraphQL endpoint for PoH subgraph +POH_SUBGRAPH_URL = "https://api.studio.thegraph.com/query/61738/proof-of-humanity-mainnet/version/latest" +LOG_FILEPATH = "logs/delete_poh_user_data.log" +logger: Logger = setup_logger(LOG_FILEPATH) + + +def get_profile_media(profile_id: str) -> tuple[None, None] | tuple[str, str]: + """ + Query the PoH subgraph to get profile picture and video CIDs + """ + query = """ + query IdQuery($id: ID!) { + submission(id: $id) { + name + status + registered + submissionTime + disputed + requests(orderBy: creationTime, orderDirection: desc, first: 1, where: {registration: true}) { + evidence(orderBy: creationTime, first: 1) { + URI + id + } + } + } + } + """ + + variables = {"id": profile_id.lower()} + + try: + response = requests.post( + POH_SUBGRAPH_URL, + json={'query': query, 'variables': variables}, + timeout=10 + ) + data = response.json() + + if 'errors' in data: + logger.info(f"Error querying subgraph: {data['errors']}") + return None, None + + submission = data.get('data', {}).get('submission') + + if not submission or not submission['requests']: + logger.info(f"No profile found with ID: {profile_id}") + return None, None + + evidence = submission['requests'][0]['evidence'][0] + registration = get_data_from_registration(evidence['URI']) + if not registration: + logger.info("Failed to fetch registration data") + return None, None + + return registration.get('photo'), registration.get('video') + + except Exception as e: + logger.info(f"Error: {e}") + return None, None + + +def get_data_from_registration(registration_uri: str) -> dict | None: + """ + Fetch and parse the registration data from the given URI. + """ + logger.info(f"Fetching registration data from {registration_uri}") + try: + response = requests.get( + f'https://cdn.kleros.link{registration_uri}', timeout=10) + if response.status_code == 200: + fileURI = response.json().get('fileURI') + try: + response = requests.get( + f'https://cdn.kleros.link{fileURI}', timeout=10) + if response.status_code == 200: + return response.json() + + logger.error( + f"Failed to fetch file data. Status code: {response.status_code}") + return None + except Exception as e: + logger.error(f"Error fetching file data: {e}") + return None + logger.error( + f"Failed to fetch registration data. Status code: {response.status_code}") + return None + except Exception as e: + logger.error(f"Error fetching registration data: {e}") + return None + + +def main(profile_id: str) -> None: + + # Validate Ethereum address format + if not profile_id.startswith('0x') or len(profile_id) != 42: + logger.error("Invalid Ethereum address format") + sys.exit(1) + + # Get media CIDs + logger.info(f"Fetching media for profile ID: {profile_id}") + photo_cid, video_cid = get_profile_media(profile_id) + + if not photo_cid and not video_cid: + logger.info("No media files found for this profile") + sys.exit(1) + logger.info(f"Found media CIDs - Photo: {photo_cid}, Video: {video_cid}") + # Delete files from Filebase + api = FilebasePinAPI(LOG_FILEPATH) + bucket_name = "kleros" + + if photo_cid: + photo_info: GetPinsResponse = api.get_file( + bucket_name, get_cid_from_uri(photo_cid)) + if photo_info: + logger.info(f"Deleting photo with CID: {photo_cid}") + request_id: str = photo_info['results'][0]['requestid'] + res: requests.Response = api.delete_pin( + bucket_name, request_id) + logger.info( + f"Photo deletion {'successful' if res.ok else 'failed'}") + else: + logger.warning("Photo CID not found in Filebase") + + else: + logger.warning("Photo CID not found in Filebase") + + if video_cid: + video_info: GetPinsResponse = api.get_file( + bucket_name, get_cid_from_uri(video_cid)) + if video_info: + logger.info(f"Deleting video with CID: {video_cid}") + request_id: str = video_info['results'][0]['requestid'] + + res: requests.Response = api.delete_pin( + bucket_name, request_id) + logger.info( + f"Video deletion {'successful' if res.ok else 'failed'}") + else: + logger.warning("Video CID not found in Filebase") + + else: + logger.warning("Video CID not found in Filebase") + + +def get_cid_from_uri(uri: str) -> str: + """ + Extract CID from a given URI. + """ + if not uri: + return '' + parts = uri.split('/') + for part in parts: + if len(part) == 46 and part.startswith('Qm'): + return part + return '' + + +if __name__ == "__main__": + # Get profile ID from user + profile: str = input( + "Enter the PoH profile ID (Ethereum address): ").strip() + main(profile) From 1ac7e7b661b0663a004acf9dd2c7a7400fc191e4 Mon Sep 17 00:00:00 2001 From: kokialgo Date: Thu, 30 Oct 2025 10:13:07 -0300 Subject: [PATCH 2/4] fix: check for all the possible buckets related to poh kleros was used by poh-v1, now we are using poh-v2 bucket --- delete_poh_user_data.py | 56 +++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/delete_poh_user_data.py b/delete_poh_user_data.py index cdba520..6b187b8 100644 --- a/delete_poh_user_data.py +++ b/delete_poh_user_data.py @@ -116,41 +116,43 @@ def main(profile_id: str) -> None: logger.info(f"Found media CIDs - Photo: {photo_cid}, Video: {video_cid}") # Delete files from Filebase api = FilebasePinAPI(LOG_FILEPATH) - bucket_name = "kleros" - - if photo_cid: - photo_info: GetPinsResponse = api.get_file( - bucket_name, get_cid_from_uri(photo_cid)) - if photo_info: - logger.info(f"Deleting photo with CID: {photo_cid}") - request_id: str = photo_info['results'][0]['requestid'] - res: requests.Response = api.delete_pin( - bucket_name, request_id) - logger.info( - f"Photo deletion {'successful' if res.ok else 'failed'}") + bucket_names = ["kleros", "poh-v2"] + + for bucket_name in bucket_names: + logger.info(f"Processing bucket: {bucket_name}") + if photo_cid: + photo_info: GetPinsResponse = api.get_file( + bucket_name, get_cid_from_uri(photo_cid)) + if photo_info: + logger.info(f"Deleting photo with CID: {photo_cid}") + request_id: str = photo_info['results'][0]['requestid'] + res: requests.Response = api.delete_pin( + bucket_name, request_id) + logger.info( + f"Photo deletion {'successful' if res.ok else 'failed'}") + else: + logger.warning("Photo CID not found in Filebase") + else: logger.warning("Photo CID not found in Filebase") - else: - logger.warning("Photo CID not found in Filebase") + if video_cid: + video_info: GetPinsResponse = api.get_file( + bucket_name, get_cid_from_uri(video_cid)) + if video_info: + logger.info(f"Deleting video with CID: {video_cid}") + request_id: str = video_info['results'][0]['requestid'] - if video_cid: - video_info: GetPinsResponse = api.get_file( - bucket_name, get_cid_from_uri(video_cid)) - if video_info: - logger.info(f"Deleting video with CID: {video_cid}") - request_id: str = video_info['results'][0]['requestid'] + res: requests.Response = api.delete_pin( + bucket_name, request_id) + logger.info( + f"Video deletion {'successful' if res.ok else 'failed'}") + else: + logger.warning("Video CID not found in Filebase") - res: requests.Response = api.delete_pin( - bucket_name, request_id) - logger.info( - f"Video deletion {'successful' if res.ok else 'failed'}") else: logger.warning("Video CID not found in Filebase") - else: - logger.warning("Video CID not found in Filebase") - def get_cid_from_uri(uri: str) -> str: """ From 894aef136012bf648cc6a04fea6050b4c703a9a9 Mon Sep 17 00:00:00 2001 From: kokialgo Date: Thu, 30 Oct 2025 10:15:02 -0300 Subject: [PATCH 3/4] fix: use env-var for log_path --- delete_poh_user_data.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/delete_poh_user_data.py b/delete_poh_user_data.py index 6b187b8..9650a7f 100644 --- a/delete_poh_user_data.py +++ b/delete_poh_user_data.py @@ -1,6 +1,7 @@ """ Script to delete PoH user data (profile picture and video) from Filebase using their profile ID. """ +import os from logging import Logger import sys import requests @@ -10,8 +11,11 @@ # GraphQL endpoint for PoH subgraph POH_SUBGRAPH_URL = "https://api.studio.thegraph.com/query/61738/proof-of-humanity-mainnet/version/latest" -LOG_FILEPATH = "logs/delete_poh_user_data.log" -logger: Logger = setup_logger(LOG_FILEPATH) +log_path: str = os.getenv('LOG_FILEPATH', '/var/log/py-kleros-ipfs') +log_filepath: str = os.path.join(log_path, 'delete_poh_user_data.log') + + +logger: Logger = setup_logger(log_filepath) def get_profile_media(profile_id: str) -> tuple[None, None] | tuple[str, str]: From 5b784b8f82be3cc73267ae4b7f97a24f91c9ff9b Mon Sep 17 00:00:00 2001 From: kokialgo Date: Thu, 30 Oct 2025 10:51:26 -0300 Subject: [PATCH 4/4] fix: output to stdout the photo_cid and video_cid --- delete_poh_user_data.py | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/delete_poh_user_data.py b/delete_poh_user_data.py index 9650a7f..3248fe4 100644 --- a/delete_poh_user_data.py +++ b/delete_poh_user_data.py @@ -4,6 +4,7 @@ import os from logging import Logger import sys +from typing import Tuple import requests from filebase_datatypes import GetPinsResponse from filebase_pin_api import FilebasePinAPI @@ -34,7 +35,7 @@ def get_profile_media(profile_id: str) -> tuple[None, None] | tuple[str, str]: evidence(orderBy: creationTime, first: 1) { URI id - } + } } } } @@ -103,9 +104,24 @@ def get_data_from_registration(registration_uri: str) -> dict | None: return None -def main(profile_id: str) -> None: +def main(profile_id: str) -> Tuple[str | None, str | None]: + """ + Main entry point for the script. Validates the Ethereum address format, + fetches media CIDs for the given profile ID, and deletes the corresponding + files from Filebase. + + Parameters + ---------- + profile_id : str + The Ethereum address of the user profile to delete media from. + + Returns + ------- + Tuple[str, str] | Tuple[None, None] + A tuple containing the CIDs of the deleted photo and video files. + If no media files are found, returns (None, None). + """ - # Validate Ethereum address format if not profile_id.startswith('0x') or len(profile_id) != 42: logger.error("Invalid Ethereum address format") sys.exit(1) @@ -117,9 +133,10 @@ def main(profile_id: str) -> None: if not photo_cid and not video_cid: logger.info("No media files found for this profile") sys.exit(1) - logger.info(f"Found media CIDs - Photo: {photo_cid}, Video: {video_cid}") + logger.info( + f"Found media CIDs - Photo: {photo_cid}, Video: {video_cid}") # Delete files from Filebase - api = FilebasePinAPI(LOG_FILEPATH) + api = FilebasePinAPI(log_filepath) bucket_names = ["kleros", "poh-v2"] for bucket_name in bucket_names: @@ -156,6 +173,7 @@ def main(profile_id: str) -> None: else: logger.warning("Video CID not found in Filebase") + return (photo_cid, video_cid) def get_cid_from_uri(uri: str) -> str: @@ -175,4 +193,6 @@ def get_cid_from_uri(uri: str) -> str: # Get profile ID from user profile: str = input( "Enter the PoH profile ID (Ethereum address): ").strip() - main(profile) + photo_cid, video_cid = main(profile) + # Send to STDOUT to be used by ansible or other scripts + print(photo_cid, video_cid)