Files
claudetools/temp/bardach_ops_2_move_unique.py
Mike Swanson fa15b03180 sync: Auto-sync from ACG-M-L5090 at 2026-03-10 19:11:00
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>
2026-03-10 19:59:08 -07:00

249 lines
9.3 KiB
Python

"""
Operation 2: Move unique contacts from Temp to Main Contacts folder (278 contacts)
Tries move endpoint first, falls back to copy+delete.
"""
import json
import subprocess
import time
import urllib.parse
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"
DATA_FILE = "D:/ClaudeTools/temp/bardach_temp_vs_main.json"
OUTPUT_FILE = "D:/ClaudeTools/temp/bardach_op2_move_unique.json"
# Contact fields to copy in fallback mode
CONTACT_FIELDS = [
"givenName", "surname", "displayName", "middleName", "nickName",
"title", "jobTitle", "companyName", "department", "officeLocation",
"businessHomePage", "personalNotes", "generation", "imAddresses",
"emailAddresses", "homePhones", "mobilePhone", "businessPhones",
"homeAddress", "businessAddress", "otherAddress",
"birthday", "yomiGivenName", "yomiSurname", "yomiCompanyName",
"fileAs", "initials", "manager", "assistantName", "profession",
"spouseName", "children"
]
def get_token():
url = f"https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/token"
data = (
f"client_id={CLIENT_ID}"
f"&scope={urllib.parse.quote(SCOPE)}"
f"&client_secret={urllib.parse.quote(CLIENT_SECRET)}"
f"&grant_type=client_credentials"
)
result = subprocess.run(
["curl", "-s", "-X", "POST", url,
"-H", "Content-Type: application/x-www-form-urlencoded",
"-d", data],
capture_output=True, text=True
)
resp = json.loads(result.stdout)
if "access_token" not in resp:
print(f"[ERROR] Token acquisition failed: {resp}")
raise Exception("Failed to get token")
print("[OK] Token acquired")
return resp["access_token"]
def get_contacts_folder_id(token):
"""Get the default Contacts folder ID."""
url = f"https://graph.microsoft.com/v1.0/users/{USER}/contactFolders"
result = subprocess.run(
["curl", "-s", "-X", "GET", url,
"-H", f"Authorization: Bearer {token}"],
capture_output=True, text=True
)
resp = json.loads(result.stdout)
for folder in resp.get("value", []):
if folder.get("displayName") == "Contacts":
return folder["id"]
return None
def try_move(token, contact_id, dest_id):
"""Try the /move endpoint."""
url = f"https://graph.microsoft.com/v1.0/users/{USER}/contacts/{contact_id}/move"
body = json.dumps({"destinationId": dest_id})
result = subprocess.run(
["curl", "-s", "-w", "\n%{http_code}", "-X", "POST", url,
"-H", f"Authorization: Bearer {token}",
"-H", "Content-Type: application/json",
"-d", body],
capture_output=True, text=True
)
lines = result.stdout.rsplit("\n", 1)
status = lines[-1].strip() if len(lines) > 1 else "000"
body_text = lines[0] if len(lines) > 1 else result.stdout
return status, body_text
def get_contact(token, contact_id):
"""Read full contact data."""
url = f"https://graph.microsoft.com/v1.0/users/{USER}/contacts/{contact_id}"
result = subprocess.run(
["curl", "-s", "-X", "GET", url,
"-H", f"Authorization: Bearer {token}"],
capture_output=True, text=True
)
return json.loads(result.stdout)
def create_contact(token, contact_data):
"""Create a contact in the default Contacts folder."""
url = f"https://graph.microsoft.com/v1.0/users/{USER}/contacts"
# Build clean payload with only writable fields
payload = {}
for field in CONTACT_FIELDS:
val = contact_data.get(field)
if val is not None and val != "" and val != [] and val != {}:
payload[field] = val
body = json.dumps(payload)
result = subprocess.run(
["curl", "-s", "-w", "\n%{http_code}", "-X", "POST", url,
"-H", f"Authorization: Bearer {token}",
"-H", "Content-Type: application/json",
"-d", body],
capture_output=True, text=True
)
lines = result.stdout.rsplit("\n", 1)
status = lines[-1].strip() if len(lines) > 1 else "000"
body_text = lines[0] if len(lines) > 1 else result.stdout
return status, body_text
def delete_contact(token, contact_id):
url = f"https://graph.microsoft.com/v1.0/users/{USER}/contacts/{contact_id}"
result = subprocess.run(
["curl", "-s", "-o", "/dev/null", "-w", "%{http_code}",
"-X", "DELETE", url,
"-H", f"Authorization: Bearer {token}"],
capture_output=True, text=True
)
return result.stdout.strip()
def main():
print("=" * 60)
print("OPERATION 2: Move unique contacts to Main Contacts (278)")
print("=" * 60)
with open(DATA_FILE, "r") as f:
data = json.load(f)
unique = data["unique_to_temp"]
total = len(unique)
print(f"[INFO] Loaded {total} unique contacts to move")
token = get_token()
# Get the contacts folder ID for move endpoint
folder_id = get_contacts_folder_id(token)
print(f"[INFO] Main Contacts folder ID: {folder_id}")
# Test move endpoint with first contact
move_works = False
if total > 0:
test_id = unique[0]["temp_id"]
# Try with "contacts" string first
status, body = try_move(token, test_id, "contacts")
if status in ("200", "201"):
move_works = True
print("[OK] Move endpoint works with 'contacts' destination")
elif folder_id:
# Try with actual folder ID
status, body = try_move(token, test_id, folder_id)
if status in ("200", "201"):
move_works = True
print("[OK] Move endpoint works with folder ID")
else:
print(f"[WARNING] Move endpoint returned {status}, falling back to copy+delete")
print(f" Response: {body[:200]}")
else:
print(f"[WARNING] Move returned {status} and no folder ID found, using copy+delete")
use_move = move_works
dest_id = "contacts" if not folder_id else folder_id
# If the first contact was already moved successfully via test, track it
start_index = 1 if move_works else 0
successes = []
failures = []
method_used = "move" if use_move else "copy+delete"
print(f"[INFO] Using method: {method_used}")
if move_works:
# First one already moved
successes.append({
"temp_id": unique[0]["temp_id"],
"displayName": unique[0].get("displayName", ""),
"method": "move"
})
start_time = time.time()
for i in range(start_index, total):
contact = unique[i]
# Re-acquire token every 250 operations
if i > 0 and i % 250 == 0:
print(f"[INFO] Re-acquiring token at operation {i}...")
token = get_token()
temp_id = contact["temp_id"]
display = contact.get("displayName", "")
if use_move:
status, body = try_move(token, temp_id, dest_id)
if status in ("200", "201"):
successes.append({"temp_id": temp_id, "displayName": display, "method": "move"})
else:
failures.append({"temp_id": temp_id, "displayName": display, "status": status, "error": body[:200]})
else:
# Fallback: copy + delete
try:
cdata = get_contact(token, temp_id)
if "error" in cdata:
failures.append({"temp_id": temp_id, "displayName": display, "status": "read_fail", "error": str(cdata["error"])[:200]})
continue
cstatus, cbody = create_contact(token, cdata)
if cstatus in ("200", "201"):
# Delete original
dstatus = delete_contact(token, temp_id)
if dstatus in ("204", "200"):
successes.append({"temp_id": temp_id, "displayName": display, "method": "copy+delete"})
else:
successes.append({"temp_id": temp_id, "displayName": display, "method": "copy_only", "delete_status": dstatus})
else:
failures.append({"temp_id": temp_id, "displayName": display, "status": cstatus, "error": cbody[:200]})
except Exception as e:
failures.append({"temp_id": temp_id, "displayName": display, "status": "exception", "error": str(e)[:200]})
# Progress every 25
if (i + 1) % 25 == 0 or (i + 1) == total:
elapsed = time.time() - start_time
rate = (i + 1) / elapsed if elapsed > 0 else 0
print(f" [{i+1}/{total}] OK={len(successes)} FAIL={len(failures)} ({rate:.1f}/sec)")
elapsed = time.time() - start_time
results = {
"operation": "move_unique_contacts",
"method": method_used,
"total": total,
"successes": len(successes),
"failures": len(failures),
"elapsed_seconds": round(elapsed, 1),
"successful_contacts": successes,
"failed_contacts": failures
}
with open(OUTPUT_FILE, "w") as f:
json.dump(results, f, indent=2)
print(f"\n[SUCCESS] Operation 2 complete")
print(f" Moved: {len(successes)}/{total}")
print(f" Failed: {len(failures)}/{total}")
print(f" Method: {method_used}")
print(f" Time: {elapsed:.1f}s")
print(f" Results: {OUTPUT_FILE}")
if __name__ == "__main__":
main()