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>
This commit is contained in:
248
temp/bardach_ops_2_move_unique.py
Normal file
248
temp/bardach_ops_2_move_unique.py
Normal file
@@ -0,0 +1,248 @@
|
||||
"""
|
||||
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()
|
||||
Reference in New Issue
Block a user