t4: consolidate seed scripts, remove demo modes, standardize passwords
api-ci-deploy / test-build-deploy (push) Has been cancelled
api-ci-deploy / test-build-deploy (push) Has been cancelled
This commit is contained in:
@@ -23,7 +23,12 @@ Uniform accounts per school (10 × 2 = 20 total)
|
||||
student3@{domain} student
|
||||
|
||||
Run from inside the ccapi container:
|
||||
python3 main.py --mode seed
|
||||
python3 main.py --mode seed-test
|
||||
|
||||
Or directly:
|
||||
python3 -c "from run.initialization.seed_environment import seed; seed()"
|
||||
python3 -c "from run.initialization.seed_environment import seed; seed(test=True)"
|
||||
"""
|
||||
import os
|
||||
import time
|
||||
@@ -49,27 +54,54 @@ GREENFIELD_DOMAIN = "greenfieldacademy.test"
|
||||
|
||||
# ─── Passwords ────────────────────────────────────────────────────────────────
|
||||
|
||||
PWD_ADMIN = "Admin@Cc2025!"
|
||||
PWD_TEACHER = "Teacher@Cc2025!"
|
||||
PWD_STUDENT = "Student@Cc2025!"
|
||||
DEFAULT_PLATFORM_ADMIN_PASSWORD = "KevlarAI2025!"
|
||||
DEFAULT_SCHOOL_ADMIN_PASSWORD = "Admin@Cc2025!"
|
||||
DEFAULT_TEACHER_PASSWORD = "Teacher@Cc2025!"
|
||||
DEFAULT_STUDENT_PASSWORD = "Student@Cc2025!"
|
||||
|
||||
|
||||
def get_seed_password(role: str) -> str:
|
||||
"""Return the seed password for a role, allowing env overrides."""
|
||||
normalized = role.lower().strip()
|
||||
env_by_role = {
|
||||
"platform_admin": ("SEED_PLATFORM_ADMIN_PASSWORD", DEFAULT_PLATFORM_ADMIN_PASSWORD),
|
||||
"school_admin": ("SEED_SCHOOL_ADMIN_PASSWORD", DEFAULT_SCHOOL_ADMIN_PASSWORD),
|
||||
"teacher": ("SEED_TEACHER_PASSWORD", DEFAULT_TEACHER_PASSWORD),
|
||||
"student": ("SEED_STUDENT_PASSWORD", DEFAULT_STUDENT_PASSWORD),
|
||||
}
|
||||
if normalized not in env_by_role:
|
||||
raise ValueError(f"Unknown seed password role: {role}")
|
||||
env_name, default = env_by_role[normalized]
|
||||
return os.getenv(env_name, default)
|
||||
|
||||
|
||||
def get_seed_passwords() -> Dict[str, str]:
|
||||
return {
|
||||
"platform_admin": get_seed_password("platform_admin"),
|
||||
"school_admin": get_seed_password("school_admin"),
|
||||
"teacher": get_seed_password("teacher"),
|
||||
"student": get_seed_password("student"),
|
||||
}
|
||||
|
||||
|
||||
# ─── Account template ────────────────────────────────────────────────────────
|
||||
|
||||
def _school_accounts(domain: str, institute_id: str) -> List[Dict]:
|
||||
passwords = get_seed_passwords()
|
||||
return [
|
||||
# school_admin accounts
|
||||
{
|
||||
"prefix": "admin", "email": f"admin@{domain}",
|
||||
"full_name": "Alex Admin", "display_name": "Alex",
|
||||
"username": f"admin.{domain.replace('.', '_')}",
|
||||
"user_type": "teacher", "role": "school_admin", "password": PWD_ADMIN,
|
||||
"user_type": "teacher", "role": "school_admin", "password": passwords["school_admin"],
|
||||
"institute_id": institute_id,
|
||||
},
|
||||
{
|
||||
"prefix": "head", "email": f"head@{domain}",
|
||||
"full_name": "Helen Head", "display_name": "Helen",
|
||||
"username": f"head.{domain.replace('.', '_')}",
|
||||
"user_type": "teacher", "role": "school_admin", "password": PWD_ADMIN,
|
||||
"user_type": "teacher", "role": "school_admin", "password": passwords["school_admin"],
|
||||
"institute_id": institute_id,
|
||||
},
|
||||
# teacher accounts
|
||||
@@ -77,35 +109,35 @@ def _school_accounts(domain: str, institute_id: str) -> List[Dict]:
|
||||
"prefix": "physics", "email": f"physics@{domain}",
|
||||
"full_name": "Phil Physics", "display_name": "Phil",
|
||||
"username": f"physics.{domain.replace('.', '_')}",
|
||||
"user_type": "teacher", "role": "teacher", "password": PWD_TEACHER,
|
||||
"user_type": "teacher", "role": "teacher", "password": passwords["teacher"],
|
||||
"institute_id": institute_id,
|
||||
},
|
||||
{
|
||||
"prefix": "maths", "email": f"maths@{domain}",
|
||||
"full_name": "Mary Maths", "display_name": "Mary",
|
||||
"username": f"maths.{domain.replace('.', '_')}",
|
||||
"user_type": "teacher", "role": "teacher", "password": PWD_TEACHER,
|
||||
"user_type": "teacher", "role": "teacher", "password": passwords["teacher"],
|
||||
"institute_id": institute_id,
|
||||
},
|
||||
{
|
||||
"prefix": "teacher1", "email": f"teacher1@{domain}",
|
||||
"full_name": "Tom Teacher", "display_name": "Tom",
|
||||
"username": f"teacher1.{domain.replace('.', '_')}",
|
||||
"user_type": "teacher", "role": "teacher", "password": PWD_TEACHER,
|
||||
"user_type": "teacher", "role": "teacher", "password": passwords["teacher"],
|
||||
"institute_id": institute_id,
|
||||
},
|
||||
{
|
||||
"prefix": "teacher2", "email": f"teacher2@{domain}",
|
||||
"full_name": "Tara Teach", "display_name": "Tara",
|
||||
"username": f"teacher2.{domain.replace('.', '_')}",
|
||||
"user_type": "teacher", "role": "teacher", "password": PWD_TEACHER,
|
||||
"user_type": "teacher", "role": "teacher", "password": passwords["teacher"],
|
||||
"institute_id": institute_id,
|
||||
},
|
||||
{
|
||||
"prefix": "teacher3", "email": f"teacher3@{domain}",
|
||||
"full_name": "Tim Teachwell", "display_name": "Tim",
|
||||
"username": f"teacher3.{domain.replace('.', '_')}",
|
||||
"user_type": "teacher", "role": "teacher", "password": PWD_TEACHER,
|
||||
"user_type": "teacher", "role": "teacher", "password": passwords["teacher"],
|
||||
"institute_id": institute_id,
|
||||
},
|
||||
# student accounts
|
||||
@@ -113,30 +145,49 @@ def _school_accounts(domain: str, institute_id: str) -> List[Dict]:
|
||||
"prefix": "student1", "email": f"student1@{domain}",
|
||||
"full_name": "Sam Student", "display_name": "Sam",
|
||||
"username": f"student1.{domain.replace('.', '_')}",
|
||||
"user_type": "student", "role": "student", "password": PWD_STUDENT,
|
||||
"user_type": "student", "role": "student", "password": passwords["student"],
|
||||
"institute_id": institute_id,
|
||||
},
|
||||
{
|
||||
"prefix": "student2", "email": f"student2@{domain}",
|
||||
"full_name": "Sophie Study", "display_name": "Sophie",
|
||||
"username": f"student2.{domain.replace('.', '_')}",
|
||||
"user_type": "student", "role": "student", "password": PWD_STUDENT,
|
||||
"user_type": "student", "role": "student", "password": passwords["student"],
|
||||
"institute_id": institute_id,
|
||||
},
|
||||
{
|
||||
"prefix": "student3", "email": f"student3@{domain}",
|
||||
"full_name": "Steve Scholar", "display_name": "Steve",
|
||||
"username": f"student3.{domain.replace('.', '_')}",
|
||||
"user_type": "student", "role": "student", "password": PWD_STUDENT,
|
||||
"user_type": "student", "role": "student", "password": passwords["student"],
|
||||
"institute_id": institute_id,
|
||||
},
|
||||
]
|
||||
|
||||
ALL_ACCOUNTS = (
|
||||
FULL_ACCOUNTS = (
|
||||
_school_accounts(KEVLARAI_DOMAIN, KEVLARAI_ID) +
|
||||
_school_accounts(GREENFIELD_DOMAIN, GREENFIELD_ID)
|
||||
)
|
||||
|
||||
|
||||
def get_accounts(test: bool = False) -> List[Dict]:
|
||||
"""Return full (20-user) or lightweight test (9-user) seed fixtures."""
|
||||
if not test:
|
||||
return list(FULL_ACCOUNTS)
|
||||
|
||||
wanted = {
|
||||
f"student1@{KEVLARAI_DOMAIN}",
|
||||
f"student2@{KEVLARAI_DOMAIN}",
|
||||
f"student3@{KEVLARAI_DOMAIN}",
|
||||
f"admin@{GREENFIELD_DOMAIN}",
|
||||
f"physics@{GREENFIELD_DOMAIN}",
|
||||
f"maths@{GREENFIELD_DOMAIN}",
|
||||
f"teacher1@{GREENFIELD_DOMAIN}",
|
||||
f"student1@{GREENFIELD_DOMAIN}",
|
||||
f"student2@{GREENFIELD_DOMAIN}",
|
||||
}
|
||||
return [account for account in FULL_ACCOUNTS if account["email"] in wanted]
|
||||
|
||||
# ─── Supabase helpers ─────────────────────────────────────────────────────────
|
||||
|
||||
def _sb_ctx():
|
||||
@@ -183,18 +234,19 @@ def _rest_patch(url, headers, table, match_col, match_val, data):
|
||||
|
||||
# ─── Main seed function ───────────────────────────────────────────────────────
|
||||
|
||||
def seed() -> Dict[str, Any]:
|
||||
def seed(test: bool = False) -> Dict[str, Any]:
|
||||
from modules.database.services.provisioning_service import ProvisioningService
|
||||
from modules.database.services.neo4j_service import Neo4jService
|
||||
from modules.database.init.init_calendar import create_calendar
|
||||
|
||||
accounts = get_accounts(test=test)
|
||||
url, headers = _sb_ctx()
|
||||
errors: List[str] = []
|
||||
results: Dict[str, Any] = {}
|
||||
results: Dict[str, Any] = {"mode": "test" if test else "full", "account_count": len(accounts)}
|
||||
|
||||
# ── Step 1: Fix KevlarAI institute record ─────────────────────────────────
|
||||
logger.info("=" * 60)
|
||||
logger.info("SEED ENVIRONMENT")
|
||||
logger.info(f"SEED ENVIRONMENT ({'test' if test else 'full'} mode)")
|
||||
logger.info("=" * 60)
|
||||
logger.info("\n[1] KevlarAI institute record...")
|
||||
try:
|
||||
@@ -272,7 +324,7 @@ def seed() -> Dict[str, Any]:
|
||||
results["global_calendar"] = "error"
|
||||
|
||||
# ── Step 5: Create / verify auth users ────────────────────────────────────
|
||||
logger.info("[5] Creating auth users (20 accounts)...")
|
||||
logger.info(f"[5] Creating auth users ({len(accounts)} accounts)...")
|
||||
try:
|
||||
existing = _auth_get(url, headers, "/users", {"per_page": 200}).get("users", [])
|
||||
existing_by_email = {u["email"]: u for u in existing}
|
||||
@@ -281,7 +333,7 @@ def seed() -> Dict[str, Any]:
|
||||
existing_by_email = {}
|
||||
|
||||
created_users: Dict[str, str] = {} # email → uid
|
||||
for spec in ALL_ACCOUNTS:
|
||||
for spec in accounts:
|
||||
email = spec["email"]
|
||||
if email in existing_by_email:
|
||||
created_users[email] = existing_by_email[email]["id"]
|
||||
@@ -311,7 +363,7 @@ def seed() -> Dict[str, Any]:
|
||||
|
||||
# ── Step 6: Upsert profiles and memberships ───────────────────────────────
|
||||
logger.info("[6] Upserting profiles and memberships...")
|
||||
for spec in ALL_ACCOUNTS:
|
||||
for spec in accounts:
|
||||
uid = created_users.get(spec["email"])
|
||||
if not uid:
|
||||
continue
|
||||
@@ -341,12 +393,18 @@ def seed() -> Dict[str, Any]:
|
||||
# ── Step 7: Merge Neo4j Teacher/Student nodes ─────────────────────────────
|
||||
logger.info("[7] Merging Neo4j worker nodes...")
|
||||
try:
|
||||
from neo4j import GraphDatabase
|
||||
driver = GraphDatabase.driver("bolt://192.168.0.209:7687", auth=("neo4j", "&%N304j&%"))
|
||||
from modules.database.tools.neo4j_driver_tools import close_driver, get_driver
|
||||
bolt_url = os.getenv("NEO4J_BOLT_URL") or os.getenv("APP_BOLT_URL")
|
||||
neo4j_user = os.getenv("NEO4J_USER") or os.getenv("USER_NEO4J")
|
||||
neo4j_password = os.getenv("NEO4J_PASSWORD") or os.getenv("PASSWORD_NEO4J")
|
||||
auth = (neo4j_user, neo4j_password) if neo4j_user and neo4j_password else None
|
||||
driver = get_driver(url=bolt_url, auth=auth) if bolt_url else get_driver()
|
||||
if driver is None:
|
||||
raise RuntimeError("Neo4j driver unavailable; check NEO4J_BOLT_URL/APP_BOLT_URL and NEO4J_PASSWORD/PASSWORD_NEO4J")
|
||||
|
||||
# Group by institute DB
|
||||
by_db: Dict[str, List[Dict]] = {}
|
||||
for spec in ALL_ACCOUNTS:
|
||||
for spec in accounts:
|
||||
uid = created_users.get(spec["email"])
|
||||
if not uid:
|
||||
continue
|
||||
@@ -369,7 +427,7 @@ def seed() -> Dict[str, Any]:
|
||||
)
|
||||
logger.info(f" [{db[:35]}] {len(users)} nodes merged ✓")
|
||||
|
||||
driver.close()
|
||||
close_driver(driver)
|
||||
results["neo4j_nodes"] = "ok"
|
||||
except Exception as e:
|
||||
errors.append(f"neo4j_nodes: {e}")
|
||||
@@ -427,7 +485,7 @@ def seed() -> Dict[str, Any]:
|
||||
results["success"] = len(errors) == 0
|
||||
results["errors"] = errors
|
||||
|
||||
_print_credential_sheet(created_users)
|
||||
_print_credential_sheet(created_users, accounts)
|
||||
|
||||
logger.info("\n" + "=" * 60)
|
||||
if errors:
|
||||
@@ -440,14 +498,16 @@ def seed() -> Dict[str, Any]:
|
||||
return results
|
||||
|
||||
|
||||
def _print_credential_sheet(created_users: Dict[str, str]):
|
||||
def _print_credential_sheet(created_users: Dict[str, str], accounts: List[Dict]):
|
||||
PAD = 36
|
||||
include_passwords = os.getenv("PRINT_SEED_CREDENTIALS", "").lower() in {"1", "true", "yes", "on"}
|
||||
logger.info("\n" + "=" * 70)
|
||||
logger.info("CREDENTIAL SHEET")
|
||||
logger.info("CREDENTIAL SHEET" + ("" if include_passwords else " (passwords redacted; set PRINT_SEED_CREDENTIALS=true to print)"))
|
||||
logger.info("=" * 70)
|
||||
logger.info(f" {'ROLE':<16} {'EMAIL':<{PAD}} PASSWORD")
|
||||
logger.info(f" {'-'*14} {'-'*(PAD-2)} -----------")
|
||||
logger.info(f" {'[platform admin]':<16} {'[email protected]':<{PAD}} KevlarAI2025!")
|
||||
platform_password = get_seed_password("platform_admin") if include_passwords else "<redacted>"
|
||||
logger.info(f" {'[platform admin]':<16} {'[email protected]':<{PAD}} {platform_password}")
|
||||
logger.info("")
|
||||
|
||||
for school_id, domain, label in [
|
||||
@@ -455,16 +515,17 @@ def _print_credential_sheet(created_users: Dict[str, str]):
|
||||
(GREENFIELD_ID, GREENFIELD_DOMAIN, "Greenfield Academy"),
|
||||
]:
|
||||
logger.info(f" [{label}]")
|
||||
for spec in ALL_ACCOUNTS:
|
||||
for spec in accounts:
|
||||
if spec["institute_id"] != school_id:
|
||||
continue
|
||||
uid = created_users.get(spec["email"], "—")
|
||||
status = f"[{uid[:8]}]" if uid != "—" else "[MISSING]"
|
||||
logger.info(f" {spec['role']:<16} {spec['email']:<{PAD}} {spec['password']} {status}")
|
||||
password = spec["password"] if include_passwords else "<redacted>"
|
||||
logger.info(f" {spec['role']:<16} {spec['email']:<{PAD}} {password} {status}")
|
||||
logger.info("")
|
||||
logger.info("=" * 70)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import json
|
||||
print(json.dumps(seed(), indent=2, default=str))
|
||||
print(json.dumps(seed(test="--test" in os.sys.argv), indent=2, default=str))
|
||||
|
||||
Reference in New Issue
Block a user