""" Seed Test Environment — idempotent full-environment setup for CC development. Creates: - kcar@kevlarai.com → platform super-admin (admin_profiles) - KevlarAI school → already exists; adds 3 student users - Greenfield Academy → new second school with full staff + students Run inside ccapi container: python3 main.py --mode seed-test Or directly: cd ~/api && python3 -c " from run.initialization.seed_test_environment import seed_test_environment import json; print(json.dumps(seed_test_environment(), indent=2)) " """ import os import time import requests import uuid from typing import Dict, Any, Optional, List from modules.logger_tool import initialise_logger logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), "default", True) # ─── Existing KevlarAI school ──────────────────────────────────────────────── KEVLARAI_INSTITUTE_ID = "6585bf91-6ae8-4d72-ab54-cddf3ba4e648" KEVLARAI_INSTITUTE_DB = "cc.institutes.6585bf916ae84d72ab54cddf3ba4e648" # ─── Second test school ────────────────────────────────────────────────────── GREENFIELD_INSTITUTE_ID = "a1b2c3d4-e5f6-7890-abcd-ef1234567890" # deterministic UUID GREENFIELD_URN = "TEST-GFA-001" GREENFIELD_NAME = "Greenfield Academy" # ─── User definitions ──────────────────────────────────────────────────────── # Format: email, password, username, full_name, display_name, user_type, role, institute_id TEST_USERS: List[Dict] = [ # ── KevlarAI students ──────────────────────────────────────────────────── { "email": "student1@kevlarai.com", "password": "Student1@KevlarAI!", "username": "student1.kevlarai", "full_name": "Alice Nguyen", "display_name": "Alice", "user_type": "student", "role": "student", "institute_id": KEVLARAI_INSTITUTE_ID, "institute_db": KEVLARAI_INSTITUTE_DB, "metadata": {"year_group": "Year 10"}, }, { "email": "student2@kevlarai.com", "password": "Student2@KevlarAI!", "username": "student2.kevlarai", "full_name": "Ben Okafor", "display_name": "Ben", "user_type": "student", "role": "student", "institute_id": KEVLARAI_INSTITUTE_ID, "institute_db": KEVLARAI_INSTITUTE_DB, "metadata": {"year_group": "Year 10"}, }, { "email": "student3@kevlarai.com", "password": "Student3@KevlarAI!", "username": "student3.kevlarai", "full_name": "Chloe Park", "display_name": "Chloe", "user_type": "student", "role": "student", "institute_id": KEVLARAI_INSTITUTE_ID, "institute_db": KEVLARAI_INSTITUTE_DB, "metadata": {"year_group": "Year 11"}, }, # ── Greenfield Academy admin ───────────────────────────────────────────── { "email": "head@greenfieldacademy.test", "password": "Admin@Greenfield1!", "username": "head.greenfield", "full_name": "Dr James Whitmore", "display_name": "Dr Whitmore", "user_type": "teacher", "role": "school_admin", "institute_id": GREENFIELD_INSTITUTE_ID, "institute_db": None, # populated after school provisioning "metadata": {}, }, # ── Greenfield teachers ────────────────────────────────────────────────── { "email": "physics@greenfieldacademy.test", "password": "Teacher1@Greenfield1!", "username": "physics.greenfield", "full_name": "Priya Sharma", "display_name": "Priya", "user_type": "teacher", "role": "teacher", "institute_id": GREENFIELD_INSTITUTE_ID, "institute_db": None, "metadata": {"subject": "Physics"}, }, { "email": "english@greenfieldacademy.test", "password": "Teacher2@Greenfield1!", "username": "english.greenfield", "full_name": "Tom Bradley", "display_name": "Tom", "user_type": "teacher", "role": "teacher", "institute_id": GREENFIELD_INSTITUTE_ID, "institute_db": None, "metadata": {"subject": "English"}, }, # ── Greenfield students ────────────────────────────────────────────────── { "email": "alice@greenfieldacademy.test", "password": "Student1@Greenfield1!", "username": "alice.greenfield", "full_name": "Alice Thornton", "display_name": "Alice T", "user_type": "student", "role": "student", "institute_id": GREENFIELD_INSTITUTE_ID, "institute_db": None, "metadata": {"year_group": "Year 9"}, }, { "email": "bob@greenfieldacademy.test", "password": "Student2@Greenfield1!", "username": "bob.greenfield", "full_name": "Bob Ivanov", "display_name": "Bob", "user_type": "student", "role": "student", "institute_id": GREENFIELD_INSTITUTE_ID, "institute_db": None, "metadata": {"year_group": "Year 9"}, }, { "email": "carol@greenfieldacademy.test", "password": "Student3@Greenfield1!", "username": "carol.greenfield", "full_name": "Carol Mensah", "display_name": "Carol", "user_type": "student", "role": "student", "institute_id": GREENFIELD_INSTITUTE_ID, "institute_db": None, "metadata": {"year_group": "Year 10"}, }, ] def seed_test_environment() -> Dict[str, Any]: from modules.database.supabase.utils.client import SupabaseServiceRoleClient from modules.database.services.provisioning_service import ProvisioningService sb_client = SupabaseServiceRoleClient() supabase_url = os.environ["SUPABASE_URL"] service_key = os.environ["SERVICE_ROLE_KEY"] headers = { "apikey": service_key, "Authorization": f"Bearer {service_key}", "Content-Type": "application/json", } def auth_get(path, params=None): r = requests.get(f"{supabase_url}/auth/v1/admin{path}", headers=headers, params=params) r.raise_for_status() return r.json() def auth_post(path, data): r = requests.post(f"{supabase_url}/auth/v1/admin{path}", headers=headers, json=data) return r def sb_upsert(table, data, on_conflict): h = {**headers, "Prefer": "resolution=merge-duplicates,return=representation"} r = requests.post( f"{supabase_url}/rest/v1/{table}", headers=h, json=data, params={"on_conflict": on_conflict}, ) return r def sb_select(table, eq_col, eq_val): r = requests.get( f"{supabase_url}/rest/v1/{table}", headers=headers, params={"select": "*", eq_col: f"eq.{eq_val}"}, ) r.raise_for_status() return r.json() errors: List[str] = [] results: Dict[str, Any] = {"steps": {}} # ── Step 1: Ensure kcar is a platform super-admin ───────────────────────── logger.info("Step 1: Platform super-admin setup...") try: kcar_id = "d9e1d1a9-04c4-4611-bb05-57babf4a9a28" # known from profiles r = sb_upsert("admin_profiles", { "id": kcar_id, "email": "kcar@kevlarai.com", "display_name": "Kevin Carroll", "admin_role": "super_admin", "is_super_admin": True, "metadata": {"seeded": True}, }, on_conflict="id") if r.status_code in (200, 201): logger.info(" kcar → admin_profiles super_admin ✓") results["steps"]["super_admin"] = "ok" else: raise Exception(f"Upsert failed: {r.text[:200]}") except Exception as e: msg = f"super_admin setup: {e}" logger.error(f" {msg}") errors.append(msg) results["steps"]["super_admin"] = "error" # ── Step 2: Provision Greenfield Academy ────────────────────────────────── logger.info("Step 2: Greenfield Academy school provisioning...") greenfield_db = None try: # Check if already exists existing = sb_select("institutes", "id", GREENFIELD_INSTITUTE_ID) if not existing: # Determine neo4j_uuid_string (same sanitization as provisioning_service) neo4j_uuid = GREENFIELD_INSTITUTE_ID.replace("-", "") r = sb_upsert("institutes", { "id": GREENFIELD_INSTITUTE_ID, "name": GREENFIELD_NAME, "urn": GREENFIELD_URN, "status": "active", "address": {"line1": "1 Academy Road", "city": "Testville", "postcode": "TE1 1ST"}, "website": "https://greenfieldacademy.test", "metadata": {"headteacher": "Dr James Whitmore", "seeded": True}, "neo4j_uuid_string": neo4j_uuid, }, on_conflict="id") if r.status_code not in (200, 201): raise Exception(f"Institute upsert: {r.text[:200]}") logger.info(f" Greenfield Academy created [{GREENFIELD_INSTITUTE_ID[:8]}]") # Provision Neo4j DB provisioner = ProvisioningService() prov_result = provisioner.ensure_school(GREENFIELD_INSTITUTE_ID) greenfield_db = prov_result.get("db_name") logger.info(f" Neo4j DB provisioned: {greenfield_db}") else: neo4j_uuid = existing[0].get("neo4j_uuid_string") or GREENFIELD_INSTITUTE_ID.replace("-", "") greenfield_db = f"cc.institutes.{neo4j_uuid}" logger.info(f" Greenfield Academy already exists → {greenfield_db}") results["steps"]["greenfield_school"] = greenfield_db except Exception as e: msg = f"greenfield_school: {e}" logger.error(f" {msg}") errors.append(msg) results["steps"]["greenfield_school"] = "error" greenfield_db = f"cc.institutes.{GREENFIELD_INSTITUTE_ID.replace('-', '')}" # Update institute_db for Greenfield users for u in TEST_USERS: if u["institute_id"] == GREENFIELD_INSTITUTE_ID: u["institute_db"] = greenfield_db # ── Step 3: Create / verify all test users ──────────────────────────────── logger.info("Step 3: Creating test users...") created_users: Dict[str, Dict] = {} try: all_users = auth_get("/users", params={"per_page": 200}).get("users", []) existing_by_email = {u["email"]: u for u in all_users} except Exception as e: msg = f"auth/users list: {e}" logger.error(msg) errors.append(msg) existing_by_email = {} for spec in TEST_USERS: email = spec["email"] if email in existing_by_email: uid = existing_by_email[email]["id"] logger.info(f" {email}: exists [{uid[:8]}]") created_users[email] = {"id": uid, **spec} continue r = auth_post("/users", { "email": email, "password": spec["password"], "email_confirm": True, "user_metadata": { "username": spec["username"], "full_name": spec["full_name"], "display_name": spec["display_name"], "user_type": spec["user_type"], }, }) if r.status_code in (200, 201): uid = r.json()["id"] logger.info(f" {email}: created [{uid[:8]}]") created_users[email] = {"id": uid, **spec} else: msg = f"create {email}: {r.text[:200]}" logger.error(f" {msg}") errors.append(msg) time.sleep(0.25) results["steps"]["users_created"] = list(created_users.keys()) # ── Step 4: Upsert profiles + memberships ───────────────────────────────── logger.info("Step 4: Upserting profiles and memberships...") for spec in TEST_USERS: u = created_users.get(spec["email"]) if not u: continue try: sb_upsert("profiles", { "id": u["id"], "email": spec["email"], "user_type": spec["user_type"], "username": spec["username"], "full_name": spec["full_name"], "display_name": spec["display_name"], "school_id": spec["institute_id"], "neo4j_sync_status": "pending", }, on_conflict="id") sb_upsert("institute_memberships", { "profile_id": u["id"], "institute_id": spec["institute_id"], "role": spec["role"], "metadata": spec.get("metadata", {}), }, on_conflict="profile_id,institute_id") except Exception as e: msg = f"profile/membership {spec['email']}: {e}" logger.error(f" {msg}") errors.append(msg) results["steps"]["profiles_memberships"] = "ok" # ── Step 5: Neo4j Teacher/Student nodes for all users ──────────────────── logger.info("Step 5: Creating Neo4j worker nodes...") try: from neo4j import GraphDatabase driver = GraphDatabase.driver("bolt://192.168.0.209:7687", auth=("neo4j", "&%N304j&%")) # Group users by institute DB by_db: Dict[str, List[Dict]] = {} for spec in TEST_USERS: u = created_users.get(spec["email"]) if not u or not spec.get("institute_db"): continue by_db.setdefault(spec["institute_db"], []).append({**spec, "uid": u["id"]}) for db, users in by_db.items(): with driver.session(database=db) as s: for u in users: label = "Teacher" if u["user_type"] == "teacher" else "Student" s.run( f"MERGE (n:{label} {{uuid_string: $uid}}) " "SET n.worker_email = $email, " " n.worker_name = $name, " " n.unique_id = $uid, " " n.user_type = $user_type, " " n.worker_type = $user_type", uid=u["uid"], email=u["email"], name=u["full_name"], user_type=u["user_type"], ) logger.info(f" [{db[:30]}] {label}: {u['email']}") driver.close() results["steps"]["neo4j_nodes"] = "ok" except Exception as e: msg = f"neo4j_nodes: {e}" logger.error(f" {msg}") errors.append(msg) results["steps"]["neo4j_nodes"] = "error" # ── Summary ─────────────────────────────────────────────────────────────── results["success"] = len(errors) == 0 results["errors"] = errors results["message"] = ( f"Seed complete — {len(created_users)} users across 2 schools" if not errors else f"{len(errors)} error(s): {errors[0]}" ) # Print credential sheet logger.info("\n" + "=" * 60) logger.info("TEST CREDENTIAL SHEET") logger.info("=" * 60) logger.info(f"{'ROLE':<20} {'EMAIL':<40} {'PASSWORD'}") logger.info("-" * 90) logger.info(f"{'[PLATFORM ADMIN]':<20} {'kcar@kevlarai.com':<40} KevlarAI2025!") logger.info("-" * 90) logger.info(f"[KevlarAI School]") for spec in TEST_USERS: if spec["institute_id"] == KEVLARAI_INSTITUTE_ID: logger.info(f" {spec['role']:<18} {spec['email']:<40} {spec['password']}") logger.info("-" * 90) logger.info(f"[Greenfield Academy]") for spec in TEST_USERS: if spec["institute_id"] == GREENFIELD_INSTITUTE_ID: logger.info(f" {spec['role']:<18} {spec['email']:<40} {spec['password']}") logger.info("=" * 60) return results