Synced files: - Quote wizard frontend (all components, hooks, types, config) - API updates (config, models, routers, schemas, services) - Client work (bg-builders, gurushow) - Scripts (BGB Lesley termination, CIPP, Datto, migration) - Temp files (Bardach contacts, VWP investigation, misc) - Credentials and session logs - Email service, PHP API, session logs Machine: ACG-M-L5090 Timestamp: 2026-03-10 19:11:00 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
162 lines
5.3 KiB
Python
162 lines
5.3 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Bardach Contact Delete: Delete remaining Temp contacts.
|
|
Treats 404 as success (contact already gone).
|
|
Merges were already completed successfully (70/70).
|
|
"""
|
|
|
|
import json
|
|
import subprocess
|
|
import time
|
|
import sys
|
|
from datetime import datetime
|
|
|
|
sys.stdout.reconfigure(line_buffering=True)
|
|
sys.stderr.reconfigure(line_buffering=True)
|
|
|
|
TENANT_ID = "dd4a82e8-85a3-44ac-8800-07945ab4d95f"
|
|
CLIENT_ID = "fabb3421-8b34-484b-bc17-e46de9703418"
|
|
CLIENT_SECRET = "~QJ8Q~NyQSs4OcGqHZyPrA2CVnq9KBfKiimntbMO"
|
|
SCOPE = "https://graph.microsoft.com/.default"
|
|
USER = "barbara@bardach.net"
|
|
BASE_URL = f"https://graph.microsoft.com/v1.0/users/{USER}/contacts"
|
|
DATA_FILE = "D:/ClaudeTools/temp/bardach_temp_vs_main.json"
|
|
THROTTLE_DELAY = 0.25 # slightly faster since deletes are simple
|
|
|
|
|
|
def get_token():
|
|
url = f"https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/token"
|
|
cmd = [
|
|
"curl", "-s", "-X", "POST", url,
|
|
"-H", "Content-Type: application/x-www-form-urlencoded",
|
|
"-d", f"client_id={CLIENT_ID}&scope={SCOPE}&client_secret={CLIENT_SECRET}&grant_type=client_credentials"
|
|
]
|
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
|
data = json.loads(result.stdout)
|
|
if "access_token" not in data:
|
|
print(f"[ERROR] Token failed: {data}")
|
|
sys.exit(1)
|
|
print(f"[OK] Token acquired at {datetime.now().strftime('%H:%M:%S')}")
|
|
return data["access_token"]
|
|
|
|
|
|
def api_delete(token, contact_id):
|
|
"""DELETE a contact. Returns status code as string."""
|
|
url = f"{BASE_URL}/{contact_id}"
|
|
cmd = [
|
|
"curl", "-s", "-o", "/dev/null", "-w", "%{http_code}",
|
|
"-X", "DELETE", url,
|
|
"-H", f"Authorization: Bearer {token}"
|
|
]
|
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
|
return result.stdout.strip()
|
|
|
|
|
|
def api_get(token, url):
|
|
cmd = [
|
|
"curl", "-s", "-X", "GET", url,
|
|
"-H", f"Authorization: Bearer {token}",
|
|
"-H", "Content-Type: application/json"
|
|
]
|
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
|
return json.loads(result.stdout)
|
|
|
|
|
|
# Load data
|
|
with open(DATA_FILE, "r", encoding="utf-8") as f:
|
|
data = json.load(f)
|
|
|
|
matches = data["matches_with_extras"]
|
|
exact_matches = data.get("exact_matches", [])
|
|
|
|
all_temp_ids = []
|
|
for m in matches:
|
|
all_temp_ids.append((m["temp_id"], m["displayName"]))
|
|
for m in exact_matches:
|
|
all_temp_ids.append((m["temp_id"], m["displayName"]))
|
|
|
|
print(f"[INFO] Total Temp contacts to delete: {len(all_temp_ids)}")
|
|
|
|
token = get_token()
|
|
deleted_ok = 0
|
|
already_gone = 0
|
|
real_errors = 0
|
|
error_details = []
|
|
|
|
for i, (tid, name) in enumerate(all_temp_ids):
|
|
if i > 0 and i % 500 == 0:
|
|
token = get_token()
|
|
if i > 0 and i % 200 == 0:
|
|
print(f" [INFO] Progress {i}/{len(all_temp_ids)}: {deleted_ok} deleted, {already_gone} already gone, {real_errors} errors")
|
|
|
|
code = api_delete(token, tid)
|
|
time.sleep(THROTTLE_DELAY)
|
|
|
|
if code in ("204", "200"):
|
|
deleted_ok += 1
|
|
elif code == "404":
|
|
already_gone += 1
|
|
else:
|
|
real_errors += 1
|
|
if real_errors <= 10:
|
|
print(f" [ERROR] {name}: HTTP {code}")
|
|
error_details.append({"name": name, "code": code, "temp_id": tid})
|
|
|
|
print(f"\n[OK] Delete complete:")
|
|
print(f" Deleted now: {deleted_ok}")
|
|
print(f" Already gone: {already_gone}")
|
|
print(f" Errors: {real_errors}")
|
|
|
|
# Verification
|
|
print("\n" + "=" * 70)
|
|
print("VERIFICATION")
|
|
print("=" * 70)
|
|
|
|
token = get_token()
|
|
|
|
# Check Temp folder
|
|
folders_url = f"https://graph.microsoft.com/v1.0/users/{USER}/contactFolders?$filter=displayName eq 'Temp'"
|
|
folders_resp = api_get(token, folders_url)
|
|
time.sleep(0.5)
|
|
|
|
if "value" in folders_resp and folders_resp["value"]:
|
|
temp_folder_id = folders_resp["value"][0]["id"]
|
|
count_url = f"https://graph.microsoft.com/v1.0/users/{USER}/contactFolders/{temp_folder_id}/contacts?$top=1&$select=displayName&$count=true"
|
|
count_resp = api_get(token, count_url)
|
|
remaining = len(count_resp.get("value", []))
|
|
odata_count = count_resp.get("@odata.count", "N/A")
|
|
has_more = "@odata.nextLink" in count_resp
|
|
print(f" Temp folder: odata.count={odata_count}, first page={remaining}, has_more={has_more}")
|
|
if remaining > 0:
|
|
print(f" First remaining: {count_resp['value'][0].get('displayName', '?')}")
|
|
else:
|
|
print(f" Temp folder: not found or empty")
|
|
|
|
# Check Main contacts
|
|
main_count_url = f"{BASE_URL}?$top=1&$select=displayName&$count=true"
|
|
main_resp = api_get(token, main_count_url)
|
|
main_odata = main_resp.get("@odata.count", "N/A")
|
|
print(f" Main contacts: odata.count={main_odata}")
|
|
|
|
# Save results
|
|
results = {
|
|
"timestamp": datetime.now().isoformat(),
|
|
"merge_step": "Completed previously: 70/70 patches successful",
|
|
"deletes": {
|
|
"total_attempted": len(all_temp_ids),
|
|
"deleted_now": deleted_ok,
|
|
"already_gone": already_gone,
|
|
"errors": real_errors,
|
|
"error_samples": error_details[:20],
|
|
},
|
|
"verification": {
|
|
"temp_odata_count": str(odata_count) if 'odata_count' in dir() else "N/A",
|
|
"main_odata_count": str(main_odata),
|
|
}
|
|
}
|
|
|
|
with open("D:/ClaudeTools/temp/bardach_merge_results.json", "w", encoding="utf-8") as f:
|
|
json.dump(results, f, indent=2, default=str)
|
|
|
|
print(f"\n[OK] Results saved to bardach_merge_results.json")
|