t4: consolidate seed scripts, remove demo modes, standardize passwords
Some checks failed
api-ci-deploy / test-build-deploy (push) Has been cancelled
Some checks failed
api-ci-deploy / test-build-deploy (push) Has been cancelled
This commit is contained in:
parent
abc90fa1b6
commit
e66c8ec291
@ -29,7 +29,7 @@ print_error() {
|
||||
|
||||
# Check if we should run initialization
|
||||
RUN_INIT="${RUN_INIT:-false}"
|
||||
INIT_MODE="${INIT_MODE:-infra}" # Default to 'infra', can be 'infra', 'full', or comma-separated list
|
||||
INIT_MODE="${INIT_MODE:-infra}" # Default to 'infra', can be 'infra', 'seed', 'seed-test', 'full', or comma-separated list
|
||||
|
||||
# If RUN_INIT is true, run initialization tasks
|
||||
if [ "$RUN_INIT" = "true" ]; then
|
||||
@ -51,21 +51,21 @@ if [ "$RUN_INIT" = "true" ]; then
|
||||
}
|
||||
print_success "Infrastructure setup completed"
|
||||
;;
|
||||
"demo-school")
|
||||
print_status "Creating demo school..."
|
||||
python3 main.py --mode demo-school || {
|
||||
print_error "Demo school creation failed!"
|
||||
"seed")
|
||||
print_status "Seeding canonical full environment..."
|
||||
python3 main.py --mode seed || {
|
||||
print_error "Seed failed!"
|
||||
exit 1
|
||||
}
|
||||
print_success "Demo school creation completed"
|
||||
print_success "Seed completed"
|
||||
;;
|
||||
"demo-users")
|
||||
print_status "Creating demo users..."
|
||||
python3 main.py --mode demo-users || {
|
||||
print_error "Demo users creation failed!"
|
||||
"seed-test")
|
||||
print_status "Seeding lightweight test environment..."
|
||||
python3 main.py --mode seed-test || {
|
||||
print_error "Seed test failed!"
|
||||
exit 1
|
||||
}
|
||||
print_success "Demo users creation completed"
|
||||
print_success "Seed test completed"
|
||||
;;
|
||||
"gais-data")
|
||||
print_status "Importing GAIS data..."
|
||||
@ -78,9 +78,7 @@ if [ "$RUN_INIT" = "true" ]; then
|
||||
"full")
|
||||
print_status "Running full initialization..."
|
||||
python3 main.py --mode infra || exit 1
|
||||
python3 main.py --mode demo-school || exit 1
|
||||
python3 main.py --mode demo-users || exit 1
|
||||
python3 main.py --mode gais-data || exit 1
|
||||
python3 main.py --mode seed || exit 1
|
||||
print_success "Full initialization completed"
|
||||
;;
|
||||
*)
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
# Helper script to run initialization tasks in production
|
||||
# Usage: ./init-production.sh [mode]
|
||||
# Modes: infra, demo-school, demo-users, gais-data, full
|
||||
# Modes: infra, seed, seed-test, gais-data, full
|
||||
|
||||
set -e
|
||||
|
||||
|
||||
59
main.py
59
main.py
@ -292,33 +292,20 @@ def run_infrastructure_mode():
|
||||
logger.error(f"Infrastructure setup failed: {str(e)}")
|
||||
return False
|
||||
|
||||
def run_demo_school_mode():
|
||||
"""Run demo school creation"""
|
||||
logger.info("Running in demo school mode")
|
||||
logger.info("Starting demo school creation...")
|
||||
|
||||
def run_seed_mode(test: bool = False):
|
||||
"""Run canonical environment seed."""
|
||||
mode = "test" if test else "full"
|
||||
logger.info(f"Running canonical seed mode ({mode})")
|
||||
try:
|
||||
from run.initialization import initialize_demo_school_mode
|
||||
initialize_demo_school_mode()
|
||||
logger.info("Demo school creation completed successfully")
|
||||
return True
|
||||
from run.initialization.seed_environment import seed
|
||||
import json
|
||||
result = seed(test=test)
|
||||
print(json.dumps(result, indent=2, default=str))
|
||||
return bool(result.get('success'))
|
||||
except Exception as e:
|
||||
logger.error(f"Demo school creation failed: {str(e)}")
|
||||
logger.error(f"Seed mode failed: {str(e)}")
|
||||
return False
|
||||
|
||||
def run_demo_users_mode():
|
||||
"""Run demo users creation"""
|
||||
logger.info("Running in demo users mode")
|
||||
logger.info("Starting demo users creation...")
|
||||
|
||||
try:
|
||||
from run.initialization import initialize_demo_users_mode
|
||||
initialize_demo_users_mode()
|
||||
logger.info("Demo users creation completed successfully")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Demo users creation failed: {str(e)}")
|
||||
return False
|
||||
|
||||
def run_gais_data_mode():
|
||||
"""Run GAIS data import"""
|
||||
@ -414,9 +401,8 @@ def parse_arguments():
|
||||
epilog="""
|
||||
Startup modes:
|
||||
infra - Setup infrastructure (Neo4j schema, calendar, Supabase buckets)
|
||||
demo-school - Create demo school (KevlarAI)
|
||||
demo-users - Create demo users
|
||||
seed-test - Seed full test environment (2 schools, all test users)
|
||||
seed - Seed canonical full environment (20 school users)
|
||||
seed-test - Seed lightweight test environment (9 school users)
|
||||
gais-data - Import GAIS data (Edubase, etc.)
|
||||
dev - Run development server with auto-reload
|
||||
prod - Run production server (for Docker/containerized deployment)
|
||||
@ -425,7 +411,7 @@ Startup modes:
|
||||
|
||||
parser.add_argument(
|
||||
'--mode', '-m',
|
||||
choices=['infra', 'demo-school', 'demo-users', 'seed-test', 'gais-data', 'dev', 'prod'],
|
||||
choices=['infra', 'seed', 'seed-test', 'gais-data', 'dev', 'prod'],
|
||||
default='dev',
|
||||
help='Startup mode (default: dev)'
|
||||
)
|
||||
@ -448,22 +434,13 @@ if __name__ == "__main__":
|
||||
success = run_infrastructure_mode()
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
elif args.mode == 'demo-school':
|
||||
# Run demo school creation
|
||||
success = run_demo_school_mode()
|
||||
elif args.mode == 'seed':
|
||||
success = run_seed_mode(test=False)
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
elif args.mode == 'demo-users':
|
||||
# Run demo users creation
|
||||
success = run_demo_users_mode()
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
|
||||
elif args.mode == 'seed-test':
|
||||
from run.initialization.seed_test_environment import seed_test_environment
|
||||
import json
|
||||
result = seed_test_environment()
|
||||
print(json.dumps(result, indent=2))
|
||||
sys.exit(0 if result.get('success') else 1)
|
||||
success = run_seed_mode(test=True)
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
elif args.mode == 'gais-data':
|
||||
# Run GAIS data import
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
from .infrastructure import initialize_infrastructure
|
||||
from .demo_school import initialize_demo_school
|
||||
from .demo_users import initialize_demo_users
|
||||
from .gais_data import import_gais_data
|
||||
from modules.logger_tool import initialise_logger
|
||||
import os
|
||||
@ -10,54 +8,32 @@ logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH
|
||||
def initialize_infrastructure_mode() -> None:
|
||||
"""Initialize infrastructure: Neo4j schema, calendar, and Supabase buckets"""
|
||||
logger.info("Starting infrastructure initialization...")
|
||||
|
||||
# 1. Initialize Neo4j database, schema, and calendar structure
|
||||
|
||||
logger.info("Step 1: Initializing Neo4j infrastructure...")
|
||||
from .neo4j import initialize_neo4j
|
||||
neo4j_result = initialize_neo4j()
|
||||
|
||||
|
||||
if not neo4j_result["success"]:
|
||||
logger.error(f"Neo4j infrastructure initialization failed: {neo4j_result['message']}")
|
||||
return
|
||||
|
||||
# 2. Initialize Supabase storage buckets
|
||||
|
||||
logger.info("Step 2: Initializing Supabase storage buckets...")
|
||||
from .buckets import initialize_buckets
|
||||
buckets_result = initialize_buckets()
|
||||
|
||||
|
||||
if not buckets_result["success"]:
|
||||
logger.error(f"Storage buckets initialization failed: {buckets_result['message']}")
|
||||
return
|
||||
|
||||
|
||||
logger.info("Infrastructure initialization completed successfully!")
|
||||
logger.info(f"Neo4j: {neo4j_result['message']}")
|
||||
logger.info(f"Buckets: {buckets_result['message']}")
|
||||
|
||||
def initialize_demo_school_mode() -> None:
|
||||
"""Initialize demo school (KevlarAI)"""
|
||||
logger.info("Starting demo school initialization...")
|
||||
result = initialize_demo_school()
|
||||
|
||||
if result["success"]:
|
||||
logger.info("Demo school initialization completed successfully")
|
||||
else:
|
||||
logger.error(f"Demo school initialization failed: {result['message']}")
|
||||
|
||||
def initialize_demo_users_mode() -> None:
|
||||
"""Initialize demo users"""
|
||||
logger.info("Starting demo users initialization...")
|
||||
result = initialize_demo_users()
|
||||
|
||||
if result["success"]:
|
||||
logger.info("Demo users initialization completed successfully")
|
||||
else:
|
||||
logger.error(f"Demo users initialization failed: {result['message']}")
|
||||
|
||||
def initialize_gais_data_mode() -> None:
|
||||
"""Initialize GAIS data import (Edubase, etc.)"""
|
||||
logger.info("Starting GAIS data import...")
|
||||
result = import_gais_data()
|
||||
|
||||
|
||||
if result["success"]:
|
||||
logger.info("GAIS data import completed successfully")
|
||||
else:
|
||||
@ -65,11 +41,7 @@ def initialize_gais_data_mode() -> None:
|
||||
|
||||
__all__ = [
|
||||
'initialize_infrastructure_mode',
|
||||
'initialize_demo_school_mode',
|
||||
'initialize_demo_users_mode',
|
||||
'initialize_gais_data_mode',
|
||||
'initialize_infrastructure',
|
||||
'initialize_demo_school',
|
||||
'initialize_demo_users',
|
||||
'import_gais_data'
|
||||
]
|
||||
]
|
||||
|
||||
@ -1,181 +0,0 @@
|
||||
"""
|
||||
Demo school initialization module for ClassroomCopilot
|
||||
Creates the KevlarAI demo school
|
||||
"""
|
||||
import os
|
||||
import json
|
||||
import requests
|
||||
from typing import Dict, Any
|
||||
from modules.logger_tool import initialise_logger
|
||||
from modules.database.services.provisioning_service import ProvisioningService
|
||||
import time
|
||||
|
||||
logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
|
||||
|
||||
class DemoSchoolInitializer:
|
||||
"""Handles demo school creation"""
|
||||
|
||||
def __init__(self, supabase_url: str, service_role_key: str):
|
||||
self.supabase_url = supabase_url
|
||||
self.service_role_key = service_role_key
|
||||
self.supabase_headers = {
|
||||
"apikey": service_role_key,
|
||||
"Authorization": f"Bearer {service_role_key}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
self.provisioning_service = ProvisioningService()
|
||||
|
||||
def create_kevlarai_school(self) -> Dict[str, Any]:
|
||||
"""Create the KevlarAI demo school"""
|
||||
logger.info("Creating KevlarAI demo school...")
|
||||
|
||||
try:
|
||||
# Check if KevlarAI school already exists
|
||||
response = self._supabase_request_with_retry(
|
||||
'get',
|
||||
f"{self.supabase_url}/rest/v1/institutes",
|
||||
headers=self.supabase_headers,
|
||||
params={
|
||||
"select": "*",
|
||||
"name": "eq.KevlarAI"
|
||||
}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
existing_schools = response.json()
|
||||
if existing_schools and len(existing_schools) > 0:
|
||||
logger.info("KevlarAI school already exists")
|
||||
school = existing_schools[0]
|
||||
try:
|
||||
self.provisioning_service.ensure_school(school["id"])
|
||||
except Exception as provisioning_error:
|
||||
logger.warning(f"Provisioning KevlarAI school failed: {provisioning_error}")
|
||||
return {
|
||||
"success": True,
|
||||
"message": "KevlarAI school already exists",
|
||||
"school": school
|
||||
}
|
||||
|
||||
# Create KevlarAI school
|
||||
school_data = {
|
||||
"name": "KevlarAI",
|
||||
"urn": "KEVLARAI001",
|
||||
"status": "active",
|
||||
"address": {
|
||||
"street": "123 Innovation Drive",
|
||||
"town": "Tech City",
|
||||
"county": "Digital County",
|
||||
"postcode": "TC1 2AI",
|
||||
"country": "United Kingdom"
|
||||
},
|
||||
"website": "https://kevlar.ai",
|
||||
"metadata": {
|
||||
"school_type": "AI and Technology",
|
||||
"phase_of_education": "Secondary and Further Education",
|
||||
"establishment_status": "Open",
|
||||
"specialization": "Artificial Intelligence, Machine Learning, Robotics"
|
||||
}
|
||||
}
|
||||
|
||||
# Insert the school
|
||||
response = self._supabase_request_with_retry('post', f"{self.supabase_url}/rest/v1/institutes", headers={**self.supabase_headers, "Prefer": "return=representation"}, json=school_data, params={"select": "*"})
|
||||
|
||||
logger.info(f"Supabase response status: {response.status_code}")
|
||||
logger.info(f"Supabase response headers: {dict(response.headers)}")
|
||||
logger.info(f"Supabase response text: {response.text}")
|
||||
|
||||
if response.status_code in (200, 201):
|
||||
try:
|
||||
data = response.json()
|
||||
school = data[0] if isinstance(data, list) and data else data
|
||||
logger.info("Successfully created KevlarAI school")
|
||||
# Ensure Neo4j provisioning is in place
|
||||
try:
|
||||
self.provisioning_service.ensure_school(school["id"])
|
||||
except Exception as provisioning_error:
|
||||
logger.warning(f"Provisioning KevlarAI school failed: {provisioning_error}")
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Successfully created KevlarAI school",
|
||||
"school": school
|
||||
}
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error(f"Failed to parse JSON response: {str(e)}")
|
||||
logger.error(f"Response text: {response.text}")
|
||||
# If the status code is successful but we can't parse JSON,
|
||||
# the school was likely created successfully
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Successfully created KevlarAI school (response not JSON)",
|
||||
"school": None
|
||||
}
|
||||
else:
|
||||
logger.error(f"Failed to create KevlarAI school: {response.text}")
|
||||
return {
|
||||
"success": False,
|
||||
"message": f"Failed to create KevlarAI school: {response.text}"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating KevlarAI school: {str(e)}")
|
||||
return {
|
||||
"success": False,
|
||||
"message": f"Error creating KevlarAI school: {str(e)}"
|
||||
}
|
||||
|
||||
def _supabase_request_with_retry(self, method, url, **kwargs):
|
||||
"""Make a request to Supabase with retry logic"""
|
||||
max_retries = 3
|
||||
retry_delay = 2 # seconds
|
||||
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
if method.lower() == 'get':
|
||||
response = requests.get(url, **kwargs)
|
||||
elif method.lower() == 'post':
|
||||
response = requests.post(url, **kwargs)
|
||||
elif method.lower() == 'put':
|
||||
response = requests.put(url, **kwargs)
|
||||
elif method.lower() == 'delete':
|
||||
response = requests.delete(url, **kwargs)
|
||||
else:
|
||||
raise ValueError(f"Unsupported HTTP method: {method}")
|
||||
|
||||
# If successful or client error (4xx), don't retry
|
||||
if response.status_code < 500:
|
||||
return response
|
||||
|
||||
# Server error (5xx), retry after delay
|
||||
logger.warning(f"Supabase server error (attempt {attempt+1}/{max_retries}): {response.status_code} - {response.text}")
|
||||
time.sleep(retry_delay * (attempt + 1)) # Exponential backoff
|
||||
|
||||
except requests.RequestException as e:
|
||||
logger.warning(f"Supabase request exception (attempt {attempt+1}/{max_retries}): {str(e)}")
|
||||
if attempt == max_retries - 1:
|
||||
raise
|
||||
time.sleep(retry_delay * (attempt + 1))
|
||||
|
||||
# If we get here, all retries failed with server errors
|
||||
raise requests.RequestException(f"Failed after {max_retries} attempts to {method} {url}")
|
||||
|
||||
def initialize_demo_school() -> Dict[str, Any]:
|
||||
"""Initialize demo school (KevlarAI)"""
|
||||
logger.info("Starting demo school initialization...")
|
||||
|
||||
supabase_url = os.getenv("SUPABASE_URL")
|
||||
service_role_key = os.getenv("SERVICE_ROLE_KEY")
|
||||
|
||||
if not supabase_url or not service_role_key:
|
||||
return {"success": False, "message": "Missing SUPABASE_URL or SERVICE_ROLE_KEY environment variables"}
|
||||
|
||||
initializer = DemoSchoolInitializer(supabase_url, service_role_key)
|
||||
|
||||
# Create KevlarAI school
|
||||
result = initializer.create_kevlarai_school()
|
||||
|
||||
if result["success"]:
|
||||
logger.info("Demo school initialization completed successfully")
|
||||
else:
|
||||
logger.error(f"Demo school initialization failed: {result['message']}")
|
||||
|
||||
return result
|
||||
@ -1,218 +0,0 @@
|
||||
"""
|
||||
Demo users initialization — creates the three canonical @kevlarai.com accounts
|
||||
and links them to the KevlarAI institute in both Supabase and Neo4j.
|
||||
|
||||
Idempotent: existing users are reused, stale .edu demo users are removed.
|
||||
Run via: python3 main.py --mode demo-users
|
||||
"""
|
||||
import os
|
||||
import requests
|
||||
import time
|
||||
from typing import Dict, Any
|
||||
from modules.logger_tool import initialise_logger
|
||||
|
||||
logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
|
||||
|
||||
INSTITUTE_DB = "cc.institutes.6585bf916ae84d72ab54cddf3ba4e648"
|
||||
INSTITUTE_ID = "6585bf91-6ae8-4d72-ab54-cddf3ba4e648"
|
||||
|
||||
DEMO_USERS = [
|
||||
{
|
||||
"email": "kcar@kevlarai.com",
|
||||
"password": "KevlarAI2025!",
|
||||
"username": "kcar",
|
||||
"full_name": "Kevin Carroll",
|
||||
"display_name": "Kevin",
|
||||
"user_type": "teacher",
|
||||
"role": "school_admin",
|
||||
},
|
||||
{
|
||||
"email": "teacher1@kevlarai.com",
|
||||
"password": "Teacher1@KevlarAI!",
|
||||
"username": "teacher1.kevlarai",
|
||||
"full_name": "Sarah Chen",
|
||||
"display_name": "Sarah",
|
||||
"user_type": "teacher",
|
||||
"role": "teacher",
|
||||
},
|
||||
{
|
||||
"email": "teacher2@kevlarai.com",
|
||||
"password": "Teacher2@KevlarAI!",
|
||||
"username": "teacher2.kevlarai",
|
||||
"full_name": "Marcus Rodriguez",
|
||||
"display_name": "Marcus",
|
||||
"user_type": "teacher",
|
||||
"role": "teacher",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def initialize_demo_users() -> Dict[str, Any]:
|
||||
"""Create/refresh canonical @kevlarai.com demo users."""
|
||||
from neo4j import GraphDatabase
|
||||
from modules.database.supabase.utils.client import SupabaseServiceRoleClient
|
||||
|
||||
sb_client = SupabaseServiceRoleClient()
|
||||
supabase_url = os.environ["SUPABASE_URL"]
|
||||
service_key = os.environ["SERVICE_ROLE_KEY"]
|
||||
auth_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=auth_headers, params=params)
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
|
||||
def auth_post(path, data):
|
||||
return requests.post(f"{supabase_url}/auth/v1/admin{path}", headers=auth_headers, json=data)
|
||||
|
||||
def auth_delete(path):
|
||||
return requests.delete(f"{supabase_url}/auth/v1/admin{path}", headers=auth_headers)
|
||||
|
||||
def sb_upsert(table, data, on_conflict=None):
|
||||
params = {}
|
||||
if on_conflict:
|
||||
params["on_conflict"] = on_conflict
|
||||
hdrs = {**auth_headers, "Prefer": "resolution=merge-duplicates,return=representation"}
|
||||
r = requests.post(f"{supabase_url}/rest/v1/{table}", headers=hdrs, json=data, params=params)
|
||||
return r
|
||||
|
||||
errors = []
|
||||
|
||||
# ── Step 1: delete stale .edu users ─────────────────────────────────────
|
||||
logger.info("Removing stale .edu demo users...")
|
||||
try:
|
||||
existing = auth_get("/users", params={"per_page": 100}).get("users", [])
|
||||
edu_users = [u for u in existing if u.get("email", "").endswith("@kevlarai.edu")]
|
||||
for u in edu_users:
|
||||
# Try direct delete first
|
||||
r = auth_delete(f"/users/{u['id']}")
|
||||
if r.status_code not in (200, 204):
|
||||
# Profile has dependent rows — clean via SQL
|
||||
_purge_profile_rows(supabase_url, service_key, u["id"])
|
||||
auth_delete(f"/users/{u['id']}")
|
||||
logger.info(f" Removed: {u['email']}")
|
||||
except Exception as e:
|
||||
logger.warning(f" .edu cleanup warning: {e}")
|
||||
|
||||
# ── Step 2: create @kevlarai.com users ───────────────────────────────────
|
||||
logger.info("Creating @kevlarai.com demo users...")
|
||||
created_users = {}
|
||||
for spec in DEMO_USERS:
|
||||
email = spec["email"]
|
||||
all_users = auth_get("/users", params={"per_page": 100}).get("users", [])
|
||||
existing_user = next((u for u in all_users if u.get("email") == email), None)
|
||||
|
||||
if existing_user:
|
||||
uid = existing_user["id"]
|
||||
logger.info(f" {email}: already 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"Failed to create {email}: {r.text[:200]}"
|
||||
logger.error(f" {msg}")
|
||||
errors.append(msg)
|
||||
time.sleep(0.3)
|
||||
|
||||
# ── Step 3: upsert profiles ──────────────────────────────────────────────
|
||||
logger.info("Upserting profiles...")
|
||||
for spec in DEMO_USERS:
|
||||
u = created_users.get(spec["email"])
|
||||
if not u:
|
||||
continue
|
||||
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": INSTITUTE_ID,
|
||||
"neo4j_sync_status": "pending",
|
||||
}, on_conflict="id")
|
||||
|
||||
# ── Step 4: upsert memberships ───────────────────────────────────────────
|
||||
logger.info("Upserting institute memberships...")
|
||||
for spec in DEMO_USERS:
|
||||
u = created_users.get(spec["email"])
|
||||
if not u:
|
||||
continue
|
||||
sb_upsert("institute_memberships", {
|
||||
"profile_id": u["id"],
|
||||
"institute_id": INSTITUTE_ID,
|
||||
"role": spec["role"],
|
||||
}, on_conflict="profile_id,institute_id")
|
||||
|
||||
# ── Step 5: Teacher nodes in Neo4j ───────────────────────────────────────
|
||||
logger.info("Creating Neo4j Teacher nodes...")
|
||||
try:
|
||||
driver = GraphDatabase.driver("bolt://192.168.0.209:7687", auth=("neo4j", "&%N304j&%"))
|
||||
new_emails = {spec["email"] for spec in DEMO_USERS}
|
||||
with driver.session(database=INSTITUTE_DB) as s:
|
||||
# Remove stale Teacher nodes
|
||||
stale = s.run("MATCH (t:Teacher) WHERE NOT t.worker_email IN $emails RETURN t.worker_email as e, t.uuid_string as u", emails=list(new_emails)).data()
|
||||
for t in stale:
|
||||
s.run("MATCH (t:Teacher {uuid_string: $u}) DETACH DELETE t", u=t["u"])
|
||||
logger.info(f" Removed stale Teacher: {t['e']}")
|
||||
# Remove duplicate Teacher nodes (same email, different UUID)
|
||||
for spec in DEMO_USERS:
|
||||
u = created_users.get(spec["email"])
|
||||
if not u:
|
||||
continue
|
||||
dupes = s.run("MATCH (t:Teacher {worker_email: $e}) WHERE t.uuid_string <> $u RETURN t.uuid_string as uid", e=spec["email"], u=u["id"]).data()
|
||||
for d in dupes:
|
||||
s.run("MATCH (t:Teacher {uuid_string: $u}) DETACH DELETE t", u=d["uid"])
|
||||
logger.info(f" Removed duplicate Teacher UUID {d['uid'][:8]} for {spec['email']}")
|
||||
# Upsert Teacher nodes
|
||||
for spec in DEMO_USERS:
|
||||
u = created_users.get(spec["email"])
|
||||
if not u:
|
||||
continue
|
||||
s.run("""
|
||||
MERGE (t:Teacher {uuid_string: $uuid})
|
||||
SET t.worker_email = $email,
|
||||
t.worker_name = $name,
|
||||
t.unique_id = $uid,
|
||||
t.user_type = 'teacher',
|
||||
t.worker_type = 'teacher'
|
||||
""", uuid=u["id"], email=spec["email"], name=spec["full_name"], uid=u["id"])
|
||||
logger.info(f" Teacher node: {spec['email']} [{u['id'][:8]}]")
|
||||
driver.close()
|
||||
except Exception as e:
|
||||
msg = f"Neo4j Teacher node setup failed: {e}"
|
||||
logger.error(msg)
|
||||
errors.append(msg)
|
||||
|
||||
return {
|
||||
"success": len(errors) == 0,
|
||||
"created": list(created_users.keys()),
|
||||
"errors": errors,
|
||||
"message": "Demo users initialized" if not errors else f"{len(errors)} errors: {errors[0]}",
|
||||
}
|
||||
|
||||
|
||||
def _purge_profile_rows(supabase_url: str, service_key: str, profile_id: str) -> None:
|
||||
"""Delete all rows referencing a profile before deleting the auth user."""
|
||||
hdrs = {"apikey": service_key, "Authorization": f"Bearer {service_key}"}
|
||||
for table, col in [("files", "uploaded_by"), ("whiteboard_rooms", "user_id"),
|
||||
("cabinet_memberships", "profile_id"), ("institute_memberships", "profile_id")]:
|
||||
requests.delete(f"{supabase_url}/rest/v1/{table}?{col}=eq.{profile_id}", headers=hdrs)
|
||||
@ -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} {'kcar@kevlarai.com':<{PAD}} KevlarAI2025!")
|
||||
platform_password = get_seed_password("platform_admin") if include_passwords else "<redacted>"
|
||||
logger.info(f" {'[platform admin]':<16} {'kcar@kevlarai.com':<{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))
|
||||
|
||||
@ -20,14 +20,17 @@ import time
|
||||
import requests
|
||||
from typing import Dict, Any, Optional, List
|
||||
|
||||
SUPA_URL = os.environ["SUPABASE_URL"]
|
||||
SERVICE_KEY = os.environ["SERVICE_ROLE_KEY"]
|
||||
API_BASE = os.environ.get("API_BASE_URL", "http://localhost:8000")
|
||||
from run.initialization.seed_environment import get_seed_password
|
||||
|
||||
GREENFIELD_ADMIN_EMAIL = "admin@greenfieldacademy.test"
|
||||
GREENFIELD_ADMIN_PWD = "Admin@Cc2025!"
|
||||
PWD_TEACHER = "Teacher@Cc2025!"
|
||||
PWD_STUDENT = "Student@Cc2025!"
|
||||
|
||||
|
||||
def _runtime_context() -> Dict[str, str]:
|
||||
return {
|
||||
"supa_url": os.environ["SUPABASE_URL"],
|
||||
"service_key": os.environ["SERVICE_ROLE_KEY"],
|
||||
"api_base": os.environ.get("API_BASE_URL", "http://localhost:8000"),
|
||||
}
|
||||
|
||||
# ─── Period templates ──────────────────────────────────────────────────────────
|
||||
|
||||
@ -145,18 +148,20 @@ STUDENT_ENROLLMENTS = {
|
||||
# ─── Helpers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
def _sb_headers() -> Dict:
|
||||
service_key = _runtime_context()["service_key"]
|
||||
return {
|
||||
"apikey": SERVICE_KEY,
|
||||
"Authorization": f"Bearer {SERVICE_KEY}",
|
||||
"apikey": service_key,
|
||||
"Authorization": f"Bearer {service_key}",
|
||||
"Content-Type": "application/json",
|
||||
"Prefer": "return=representation",
|
||||
}
|
||||
|
||||
|
||||
def _sign_in(email: str, password: str) -> str:
|
||||
ctx = _runtime_context()
|
||||
r = requests.post(
|
||||
f"{SUPA_URL}/auth/v1/token?grant_type=password",
|
||||
headers={"apikey": SERVICE_KEY, "Content-Type": "application/json"},
|
||||
f"{ctx['supa_url']}/auth/v1/token?grant_type=password",
|
||||
headers={"apikey": ctx["service_key"], "Content-Type": "application/json"},
|
||||
json={"email": email, "password": password},
|
||||
)
|
||||
r.raise_for_status()
|
||||
@ -164,8 +169,9 @@ def _sign_in(email: str, password: str) -> str:
|
||||
|
||||
|
||||
def _api(token: str, method: str, path: str, body: Dict = None) -> Dict:
|
||||
api_base = _runtime_context()["api_base"]
|
||||
h = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
|
||||
r = getattr(requests, method)(f"{API_BASE}{path}", headers=h, json=body)
|
||||
r = getattr(requests, method)(f"{api_base}{path}", headers=h, json=body)
|
||||
try:
|
||||
return r.json()
|
||||
except Exception:
|
||||
@ -174,8 +180,9 @@ def _api(token: str, method: str, path: str, body: Dict = None) -> Dict:
|
||||
|
||||
def _get_profile_id(email: str) -> Optional[str]:
|
||||
"""Look up a profile's UUID by email via Supabase service role."""
|
||||
supa_url = _runtime_context()["supa_url"]
|
||||
r = requests.get(
|
||||
f"{SUPA_URL}/rest/v1/profiles",
|
||||
f"{supa_url}/rest/v1/profiles",
|
||||
headers=_sb_headers(),
|
||||
params={"email": f"eq.{email}", "select": "id", "limit": "1"},
|
||||
)
|
||||
@ -185,8 +192,9 @@ def _get_profile_id(email: str) -> Optional[str]:
|
||||
|
||||
def _get_teacher_timetable_id(profile_id: str) -> Optional[str]:
|
||||
"""Return the Supabase teacher_timetables.id for a given profile."""
|
||||
supa_url = _runtime_context()["supa_url"]
|
||||
r = requests.get(
|
||||
f"{SUPA_URL}/rest/v1/teacher_timetables",
|
||||
f"{supa_url}/rest/v1/teacher_timetables",
|
||||
headers=_sb_headers(),
|
||||
params={"profile_id": f"eq.{profile_id}", "select": "id", "limit": "1"},
|
||||
)
|
||||
@ -196,10 +204,11 @@ def _get_teacher_timetable_id(profile_id: str) -> Optional[str]:
|
||||
|
||||
def _patch_slot_class_ids(teacher_tt_sb_id: str, class_code_to_id: Dict[str, str]) -> int:
|
||||
"""Update class_id FK on teacher_timetable_slots rows via Supabase service role."""
|
||||
supa_url = _runtime_context()["supa_url"]
|
||||
patched = 0
|
||||
for code, class_uuid in class_code_to_id.items():
|
||||
r = requests.patch(
|
||||
f"{SUPA_URL}/rest/v1/teacher_timetable_slots",
|
||||
f"{supa_url}/rest/v1/teacher_timetable_slots",
|
||||
headers=_sb_headers(),
|
||||
params={
|
||||
"teacher_timetable_id": f"eq.{teacher_tt_sb_id}",
|
||||
@ -224,7 +233,7 @@ def seed() -> Dict[str, Any]:
|
||||
# ── [1] Sign in as Greenfield admin ───────────────────────────────────────
|
||||
print("\n[1] Signing in as admin@greenfieldacademy.test...")
|
||||
try:
|
||||
admin_token = _sign_in(GREENFIELD_ADMIN_EMAIL, GREENFIELD_ADMIN_PWD)
|
||||
admin_token = _sign_in(GREENFIELD_ADMIN_EMAIL, get_seed_password("school_admin"))
|
||||
print(" ✓ signed in")
|
||||
except Exception as e:
|
||||
return {"success": False, "error": str(e)}
|
||||
@ -331,7 +340,7 @@ def seed() -> Dict[str, Any]:
|
||||
|
||||
for teacher_email, slot_tuples in TEACHER_SLOTS.items():
|
||||
try:
|
||||
teacher_token = _sign_in(teacher_email, PWD_TEACHER)
|
||||
teacher_token = _sign_in(teacher_email, get_seed_password("teacher"))
|
||||
except Exception as e:
|
||||
err = f"login {teacher_email}: {e}"
|
||||
print(f" ✗ {err}")
|
||||
@ -439,7 +448,7 @@ def seed() -> Dict[str, Any]:
|
||||
results["materialize"] = {}
|
||||
for teacher_email in TEACHER_SLOTS:
|
||||
try:
|
||||
teacher_token = _sign_in(teacher_email, PWD_TEACHER)
|
||||
teacher_token = _sign_in(teacher_email, get_seed_password("teacher"))
|
||||
except Exception as e:
|
||||
err = f"login {teacher_email}: {e}"
|
||||
print(f" ✗ {err}")
|
||||
|
||||
@ -1,408 +1,15 @@
|
||||
"""
|
||||
Seed Test Environment — idempotent full-environment setup for CC development.
|
||||
"""Compatibility wrapper for the canonical seed environment test mode."""
|
||||
from typing import Any, Dict
|
||||
|
||||
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"},
|
||||
},
|
||||
]
|
||||
from run.initialization.seed_environment import seed
|
||||
|
||||
|
||||
def seed_test_environment() -> Dict[str, Any]:
|
||||
from modules.database.supabase.utils.client import SupabaseServiceRoleClient
|
||||
from modules.database.services.provisioning_service import ProvisioningService
|
||||
"""Seed the lightweight 9-user test environment."""
|
||||
return seed(test=True)
|
||||
|
||||
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",
|
||||
}
|
||||
if __name__ == "__main__":
|
||||
import 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
|
||||
print(json.dumps(seed_test_environment(), indent=2, default=str))
|
||||
|
||||
84
start.sh
84
start.sh
@ -2,7 +2,7 @@
|
||||
|
||||
# ClassroomCopilot Startup Script
|
||||
# Usage: ./start.sh [start_mode]
|
||||
# start_mode options: infra, demo-school, demo-users, gais-data, full, dev, prod
|
||||
# start_mode options: infra, seed, seed-test, gais-data, full, dev, prod
|
||||
|
||||
set -e
|
||||
|
||||
@ -14,10 +14,10 @@ show_help() {
|
||||
echo ""
|
||||
echo "Start modes:"
|
||||
echo " infra - Setup infrastructure (Neo4j schema, calendar, Supabase buckets)"
|
||||
echo " demo-school - Create demo school (KevlarAI)"
|
||||
echo " demo-users - Create demo users"
|
||||
echo " seed - Seed canonical full environment (20 school users)"
|
||||
echo " seed-test - Seed lightweight test environment (9 school users)"
|
||||
echo " gais-data - Import GAIS data (Edubase, etc.)"
|
||||
echo " full - Run full initialization (infra → demo-school → demo-users → gais-data)"
|
||||
echo " full - Run full initialization (infra → seed)"
|
||||
echo " nuke - 💥 NUKE Redis - Clear all queue data for fresh start"
|
||||
echo " dev - Run development server with auto-reload"
|
||||
echo " prod - Run production server (for Docker/containerized deployment)"
|
||||
@ -25,8 +25,8 @@ show_help() {
|
||||
echo "Examples:"
|
||||
echo " ./start.sh # Run in dev mode (default)"
|
||||
echo " ./start.sh infra # Setup infrastructure"
|
||||
echo " ./start.sh demo-school # Create demo school"
|
||||
echo " ./start.sh demo-users # Create demo users"
|
||||
echo " ./start.sh seed # Seed canonical full environment"
|
||||
echo " ./start.sh seed-test # Seed lightweight test environment"
|
||||
echo " ./start.sh gais-data # Import GAIS data"
|
||||
echo " ./start.sh full # Run full initialization"
|
||||
echo " ./start.sh full --yes # Run full initialization without prompts"
|
||||
@ -133,54 +133,32 @@ run_infra() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to run demo school creation
|
||||
run_demo_school() {
|
||||
print_status "Running demo school creation mode..."
|
||||
print_status "This will create the KevlarAI demo school."
|
||||
# Function to run canonical environment seed
|
||||
run_seed() {
|
||||
local mode=${1:-seed}
|
||||
if [[ "$mode" == "seed-test" ]]; then
|
||||
print_status "Running lightweight seed-test mode (9 school users)..."
|
||||
else
|
||||
print_status "Running canonical seed mode (20 school users)..."
|
||||
fi
|
||||
|
||||
# Check if we should proceed
|
||||
if [[ "$AUTO_YES" != true ]]; then
|
||||
read -p "Do you want to continue with demo school creation? (y/N): " -n 1 -r
|
||||
read -p "Do you want to continue with $mode? (y/N): " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
print_status "Demo school creation cancelled."
|
||||
print_status "$mode cancelled."
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
print_status "Starting demo school creation process..."
|
||||
$PYTHON_CMD main.py --mode demo-school
|
||||
print_status "Starting $mode process..."
|
||||
$PYTHON_CMD main.py --mode "$mode"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
print_success "Demo school creation completed successfully!"
|
||||
print_success "$mode completed successfully!"
|
||||
else
|
||||
print_error "Demo school creation failed!"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to run demo users creation
|
||||
run_demo_users() {
|
||||
print_status "Running demo users creation mode..."
|
||||
print_status "This will create demo users for testing."
|
||||
|
||||
# Check if we should proceed
|
||||
if [[ "$AUTO_YES" != true ]]; then
|
||||
read -p "Do you want to continue with demo users creation? (y/N): " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
print_status "Demo users creation cancelled."
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
print_status "Starting demo users creation process..."
|
||||
$PYTHON_CMD main.py --mode demo-users
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
print_success "Demo users creation completed successfully!"
|
||||
else
|
||||
print_error "Demo users creation failed!"
|
||||
print_error "$mode failed!"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
@ -274,7 +252,7 @@ except Exception as e:
|
||||
|
||||
# Function to run full initialization (all steps in order)
|
||||
run_full() {
|
||||
print_status "Running full initialization (infra → demo-school → demo-users → gais-data)..."
|
||||
print_status "Running full initialization (infra → seed)..."
|
||||
|
||||
# Single confirmation for the whole flow
|
||||
if [[ "$AUTO_YES" != true ]]; then
|
||||
@ -289,14 +267,8 @@ run_full() {
|
||||
# Run infra
|
||||
run_infra || { print_error "Full init aborted during infra."; exit 1; }
|
||||
|
||||
# Run demo school
|
||||
run_demo_school || { print_error "Full init aborted during demo-school."; exit 1; }
|
||||
|
||||
# Run demo users
|
||||
run_demo_users || { print_error "Full init aborted during demo-users."; exit 1; }
|
||||
|
||||
# Run GAIS data import
|
||||
run_gais_data || { print_error "Full init aborted during gais-data."; exit 1; }
|
||||
# Run canonical full seed
|
||||
run_seed seed || { print_error "Full init aborted during seed."; exit 1; }
|
||||
|
||||
print_success "Full initialization completed successfully!"
|
||||
}
|
||||
@ -383,11 +355,11 @@ main() {
|
||||
"infra")
|
||||
run_infra
|
||||
;;
|
||||
"demo-school")
|
||||
run_demo_school
|
||||
"seed")
|
||||
run_seed seed
|
||||
;;
|
||||
"demo-users")
|
||||
run_demo_users
|
||||
"seed-test")
|
||||
run_seed seed-test
|
||||
;;
|
||||
"gais-data")
|
||||
run_gais_data
|
||||
@ -406,7 +378,7 @@ main() {
|
||||
;;
|
||||
*)
|
||||
print_error "Invalid start mode: $START_MODE"
|
||||
print_status "Valid modes: infra, demo-school, demo-users, gais-data, nuke, dev, prod"
|
||||
print_status "Valid modes: infra, seed, seed-test, gais-data, full, nuke, dev, prod"
|
||||
print_status "Usage: ./start.sh [start_mode]"
|
||||
print_status "Use './start.sh --help' for more information"
|
||||
exit 1
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user