This commit is contained in:
2025-11-14 14:47:19 +00:00
parent 2a85845835
commit 46b2319e2d
199 changed files with 607543 additions and 11147 deletions
+65 -16
View File
@@ -1,26 +1,75 @@
from .manager import InitializationManager
from .initialization import InitializationSystem
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
logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
def initialize_system() -> None:
"""Initialize the system if needed"""
init_manager = InitializationManager()
def initialize_infrastructure_mode() -> None:
"""Initialize infrastructure: Neo4j schema, calendar, and Supabase buckets"""
logger.info("Starting infrastructure initialization...")
if not init_manager.check_initialization_needed():
logger.info("No initialization needed")
# 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
logger.info("Starting system initialization...")
init_system = InitializationSystem(init_manager)
success = init_system.run()
# 2. Initialize Supabase storage buckets
logger.info("Step 2: Initializing Supabase storage buckets...")
from .buckets import initialize_buckets
buckets_result = initialize_buckets()
if success:
logger.info("System initialization completed successfully")
else:
logger.error("System initialization failed")
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']}")
__all__ = ['initialize_system', 'InitializationManager', 'InitializationSystem']
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:
logger.error(f"GAIS data import failed: {result['message']}")
__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'
]
+109
View File
@@ -0,0 +1,109 @@
import os
from modules.logger_tool import initialise_logger
from modules.database.supabase.utils.client import SupabaseServiceRoleClient, CreateBucketOptions
logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
def initialize_buckets() -> dict:
"""
Initialize storage buckets for ClassroomCopilot documents and files.
Creates buckets for:
- TLDraw snapshot JSON files
- Office document files (PDF, DOCX, etc.)
- Docling document JSON files
- Document page images (PNG/base64)
- Document page fragment images (smaller PNG/base64)
Returns:
dict: Result status and message
"""
logger.info("Starting storage bucket initialization...")
try:
storage_client = SupabaseServiceRoleClient()
# Define the buckets to create
buckets = [
# TLDraw snapshot files
{
"id": "cc.public.snapshots",
"options": CreateBucketOptions(
name="ClassroomCopilot Public TLDraw Snapshots",
public=False,
file_size_limit=1000 * 1024 * 1024, # 1GB
allowed_mime_types=[
'application/json'
]
)
},
# User cabinet files
{
"id": "cc.users",
"options": CreateBucketOptions(
name="Classroom Copilot Users - Private",
public=False,
file_size_limit=1000 * 1024 * 1024, # 1GB
)
},
]
results = {}
success_count = 0
total_count = len(buckets)
for bucket in buckets:
try:
logger.info(f"Creating bucket: {bucket['id']}")
result = storage_client.create_bucket(bucket["id"], bucket["options"])
if result:
results[bucket["id"]] = {
"status": "success",
"result": result
}
success_count += 1
logger.info(f"Successfully created bucket: {bucket['id']}")
else:
results[bucket["id"]] = {
"status": "error",
"error": "Failed to create bucket"
}
logger.error(f"Failed to create bucket: {bucket['id']}")
except Exception as e:
results[bucket["id"]] = {
"status": "error",
"error": str(e)
}
logger.error(f"Error creating bucket {bucket['id']}: {str(e)}")
# Determine overall success
if success_count == total_count:
message = f"All {total_count} storage buckets created successfully"
success = True
elif success_count > 0:
message = f"Created {success_count}/{total_count} storage buckets. Some failed."
success = False
else:
message = f"Failed to create any storage buckets ({total_count} attempted)"
success = False
logger.info(f"Bucket initialization completed: {message}")
return {
"success": success,
"message": message,
"results": results,
"success_count": success_count,
"total_count": total_count
}
except Exception as e:
error_msg = f"Failed to initialize storage buckets: {str(e)}"
logger.error(error_msg)
return {
"success": False,
"message": error_msg,
"error": str(e)
}
+181
View File
@@ -0,0 +1,181 @@
"""
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
+395
View File
@@ -0,0 +1,395 @@
"""
Demo users initialization module for ClassroomCopilot
Creates demo teachers and students
"""
import os
import json
import requests
import time
from typing import Dict, Any
from modules.logger_tool import initialise_logger
from modules.database.services.provisioning_service import ProvisioningService
logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
class DemoUsersInitializer:
"""Handles demo users 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_demo_users(self) -> Dict[str, Any]:
"""Create demo teachers and students"""
logger.info("Creating demo users...")
try:
# Define demo users
demo_users = [
# Demo Teachers
{
"email": "[email protected]",
"password": "DemoTeacher123!",
"email_confirm": True,
"user_metadata": {
"name": "Dr. Sarah Chen",
"username": "sarah.chen",
"full_name": "Dr. Sarah Chen",
"display_name": "Dr. Chen",
"user_type": "teacher"
},
"app_metadata": {
"provider": "email",
"providers": ["email"]
}
},
{
"email": "[email protected]",
"password": "DemoTeacher123!",
"email_confirm": True,
"user_metadata": {
"name": "Prof. Marcus Rodriguez",
"username": "marcus.rodriguez",
"full_name": "Professor Marcus Rodriguez",
"display_name": "Prof. Rodriguez",
"user_type": "teacher"
},
"app_metadata": {
"provider": "email",
"providers": ["email"]
}
},
# Demo Students
{
"email": "[email protected]",
"password": "DemoStudent123!",
"email_confirm": True,
"user_metadata": {
"name": "Alex Thompson",
"username": "alex.thompson",
"full_name": "Alex Thompson",
"display_name": "Alex",
"user_type": "student"
},
"app_metadata": {
"provider": "email",
"providers": ["email"]
}
},
{
"email": "[email protected]",
"password": "DemoStudent123!",
"email_confirm": True,
"user_metadata": {
"name": "Jordan Lee",
"username": "jordan.lee",
"full_name": "Jordan Lee",
"display_name": "Jordan",
"user_type": "student"
},
"app_metadata": {
"provider": "email",
"providers": ["email"]
}
}
]
created_users = []
failed_users = []
for user_data in demo_users:
try:
# Create user via Auth API
response = self._supabase_request_with_retry(
'post',
f"{self.supabase_url}/auth/v1/admin/users",
headers=self.supabase_headers,
json=user_data
)
if response.status_code in (200, 201):
user = response.json()
user_id = user.get("id")
# Wait a moment for user to be created
time.sleep(1)
# Create profile
profile_data = {
"id": user_id,
"email": user_data["email"],
"user_type": user_data["user_metadata"]["user_type"],
"username": user_data["user_metadata"]["username"],
"full_name": user_data["user_metadata"]["full_name"],
"display_name": user_data["user_metadata"]["display_name"]
}
profile_response = self._supabase_request_with_retry(
'post',
f"{self.supabase_url}/rest/v1/profiles",
headers=self.supabase_headers,
json=profile_data
)
if profile_response.status_code in (200, 201):
created_users.append({
"id": user_id,
"email": user_data["email"],
"user_type": user_data["user_metadata"]["user_type"],
"username": user_data["user_metadata"]["username"]
})
logger.info(f"Successfully created user: {user_data['email']}")
else:
logger.warning(f"Failed to create profile for {user_data['email']}: {profile_response.text}")
failed_users.append({
"email": user_data["email"],
"error": f"Profile creation failed: {profile_response.text}"
})
else:
logger.warning(f"Failed to create user {user_data['email']}: {response.text}")
failed_users.append({
"email": user_data["email"],
"error": f"User creation failed: {response.text}"
})
except Exception as e:
logger.error(f"Error creating user {user_data['email']}: {str(e)}")
failed_users.append({
"email": user_data["email"],
"error": str(e)
})
# Create institute memberships for KevlarAI and provision users
all_users_to_provision = []
# Add newly created users
if created_users:
all_users_to_provision.extend(created_users)
self._create_institute_memberships(created_users)
# Also provision existing users that failed due to email_exists
existing_users = []
for failed_user in failed_users:
if "email_exists" in failed_user.get("error", ""):
# Get the existing user ID from Supabase
existing_user_id = self._get_existing_user_id(failed_user["email"])
if existing_user_id:
existing_users.append({
"id": existing_user_id,
"email": failed_user["email"],
"user_type": self._get_user_type_from_email(failed_user["email"]),
"username": self._get_username_from_email(failed_user["email"])
})
if existing_users:
logger.info(f"Found {len(existing_users)} existing users to provision")
all_users_to_provision.extend(existing_users)
self._create_institute_memberships(existing_users)
# Provision all users (new and existing)
if all_users_to_provision:
self._provision_users(all_users_to_provision)
logger.info(f"Demo users creation completed: {len(created_users)} created, {len(failed_users)} failed")
return {
"success": True,
"message": f"Successfully created {len(created_users)} demo users",
"created_users": created_users,
"failed_users": failed_users
}
except Exception as e:
logger.error(f"Error creating demo users: {str(e)}")
return {
"success": False,
"message": f"Error creating demo users: {str(e)}"
}
def _create_institute_memberships(self, users: list) -> None:
"""Create institute memberships for users in KevlarAI"""
logger.info("Creating institute memberships for demo users...")
try:
# Get KevlarAI institute ID
response = self._supabase_request_with_retry(
'get',
f"{self.supabase_url}/rest/v1/institutes",
headers=self.supabase_headers,
params={
"select": "id",
"name": "eq.KevlarAI"
}
)
if response.status_code != 200:
logger.warning("Could not get KevlarAI institute ID for memberships")
return
institutes = response.json()
if not institutes:
logger.warning("KevlarAI institute not found for memberships")
return
institute_id = institutes[0]["id"]
# Get user profile IDs
for user in users:
try:
profile_response = self._supabase_request_with_retry(
'get',
f"{self.supabase_url}/rest/v1/profiles",
headers=self.supabase_headers,
params={
"select": "id",
"email": f"eq.{user['email']}"
}
)
if profile_response.status_code == 200:
profiles = profile_response.json()
if profiles:
profile_id = profiles[0]["id"]
# Create membership
membership_data = {
"profile_id": profile_id,
"institute_id": institute_id,
"role": user["user_type"]
}
membership_response = self._supabase_request_with_retry(
'post',
f"{self.supabase_url}/rest/v1/institute_memberships",
headers=self.supabase_headers,
json=membership_data
)
if membership_response.status_code in (200, 201):
logger.info(f"Created membership for {user['email']} in KevlarAI")
else:
logger.warning(f"Failed to create membership for {user['email']}: {membership_response.text}")
except Exception as e:
logger.warning(f"Error creating membership for {user['email']}: {str(e)}")
except Exception as e:
logger.warning(f"Error creating institute memberships: {str(e)}")
def _get_existing_user_id(self, email: str) -> str:
"""Get the user ID for an existing user by email"""
try:
response = self._supabase_request_with_retry(
'get',
f"{self.supabase_url}/rest/v1/profiles",
headers=self.supabase_headers,
params={
"select": "id",
"email": f"eq.{email}"
}
)
if response.status_code == 200:
profiles = response.json()
if profiles and len(profiles) > 0:
return profiles[0].get("id")
logger.warning(f"Could not find existing user ID for {email}")
return None
except Exception as e:
logger.warning(f"Error getting existing user ID for {email}: {str(e)}")
return None
def _get_user_type_from_email(self, email: str) -> str:
"""Get user type from email based on demo user definitions"""
if "teacher" in email:
return "teacher"
elif "student" in email:
return "student"
return "teacher" # default
def _get_username_from_email(self, email: str) -> str:
"""Get username from email based on demo user definitions"""
username_map = {
"[email protected]": "sarah.chen",
"[email protected]": "marcus.rodriguez",
"[email protected]": "alex.thompson",
"[email protected]": "jordan.lee"
}
return username_map.get(email, email.split("@")[0])
def _provision_users(self, users: list) -> None:
"""Provision Neo4j databases for the created demo users."""
for user in users:
user_id = user.get("id")
if not user_id:
continue
try:
self.provisioning_service.ensure_user(user_id)
logger.info(f"Provisioned Neo4j resources for {user.get('email')}")
except Exception as exc:
logger.warning(f"Failed to provision Neo4j resources for {user.get('email')}: {exc}")
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_users() -> Dict[str, Any]:
"""Initialize demo users"""
logger.info("Starting demo users 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 = DemoUsersInitializer(supabase_url, service_role_key)
# Create demo users
result = initializer.create_demo_users()
if result["success"]:
logger.info("Demo users initialization completed successfully")
else:
logger.error(f"Demo users initialization failed: {result['message']}")
return result
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+58
View File
@@ -0,0 +1,58 @@
"""
Infrastructure initialization module for ClassroomCopilot
Handles Neo4j database, schema, calendar structure, and Supabase storage buckets
"""
import os
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)
def initialize_infrastructure() -> Dict[str, Any]:
"""
Initialize infrastructure: Neo4j database, schema, calendar structure, and Supabase buckets
Returns:
dict: Result status and message
"""
logger.info("Starting infrastructure initialization...")
try:
# 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"]:
return {
"success": False,
"message": f"Neo4j infrastructure initialization failed: {neo4j_result['message']}"
}
# 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"]:
return {
"success": False,
"message": f"Storage buckets initialization failed: {buckets_result['message']}"
}
logger.info("Infrastructure initialization completed successfully!")
return {
"success": True,
"message": "Infrastructure initialization completed successfully",
"neo4j": neo4j_result,
"buckets": buckets_result
}
except Exception as e:
error_msg = f"Failed to initialize infrastructure: {str(e)}"
logger.error(error_msg)
return {
"success": False,
"message": error_msg,
"error": str(e)
}
File diff suppressed because it is too large Load Diff
-146
View File
@@ -1,146 +0,0 @@
"""
Initialization manager for ClassroomCopilot
"""
import os
import json
from typing import Dict
import requests
from modules.logger_tool import initialise_logger
logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
class InitializationManager:
def __init__(self):
self.init_dir = os.getenv("BACKEND_INIT_PATH")
self.status_file = os.path.join(self.init_dir, "status.json")
self.data_dir = os.path.join(self.init_dir, "data")
self.supabase_url = os.getenv("SUPABASE_URL")
# Ensure directories exist
os.makedirs(self.init_dir, exist_ok=True)
os.makedirs(self.data_dir, exist_ok=True)
self.supabase_headers = {
"apikey": os.getenv("SERVICE_ROLE_KEY"),
"Authorization": f"Bearer {os.getenv('SERVICE_ROLE_KEY')}",
"Content-Type": "application/json"
}
# Define default status structure
self.default_status = {
"super_admin_created": False,
"admin_token_obtained": False,
"storage": {
"initialized": False,
"buckets": {
"cc.users": False,
"cc.institutes": False
}
},
"neo4j": {
"initialized": False,
"database_created": False,
"schema_initialized": False,
"schools_imported": False
},
"completed": False,
"timestamp": None,
"steps": []
}
self.status = self._load_status()
def _load_status(self) -> Dict:
"""Load or create initialization status"""
try:
with open(self.status_file, "r") as f:
status = json.load(f)
# Update with any missing keys
def update_dict(current: Dict, default: Dict) -> Dict:
for key, value in default.items():
if key not in current:
current[key] = value
elif isinstance(value, dict) and isinstance(current[key], dict):
current[key] = update_dict(current[key], value)
return current
status = update_dict(status, self.default_status)
self._save_status(status)
return status
except (FileNotFoundError, json.JSONDecodeError):
self._save_status(self.default_status)
return self.default_status.copy()
def _save_status(self, status: Dict) -> None:
"""Save status to file"""
os.makedirs(os.path.dirname(self.status_file), exist_ok=True)
with open(self.status_file, "w") as f:
json.dump(status, f, indent=2)
def check_admin_exists(self) -> bool:
"""Check if super admin already exists"""
try:
responseURL = f"{self.supabase_url}/auth/v1/admin/users"
logger.info(f"Checking admin existence at: {responseURL}")
response = requests.get(
responseURL,
headers=self.supabase_headers
)
if response.status_code != 200:
logger.error(f"Error checking admin existence: {response.status_code}")
return False
data = response.json()
# Fix: response format is {'users': [...], 'aud': 'authenticated'}
users = data.get('users', [])
if not isinstance(users, list):
logger.error(f"Unexpected users format: {users}")
return False
admin_email = os.getenv('ADMIN_EMAIL')
# Check for admin in users
admin_user = next(
(user for user in users
if user.get("email") == admin_email
and user.get("app_metadata", {}).get("role") == "supabase_admin"),
None
)
if admin_user:
logger.info(f"Super admin {admin_email} already exists")
return True
return False
except Exception as e:
logger.error(f"Error checking admin existence: {str(e)}")
return False
def check_initialization_needed(self) -> bool:
"""Check if initialization is needed"""
# First check if admin exists
if self.check_admin_exists():
logger.info("Super admin exists, skipping initialization")
return False
# Then check status file
if self.status.get("completed"):
logger.info("Initialization already completed")
return False
# Check if any step needs completion
incomplete = not all(
v for k, v in self.status.items()
if k not in ("timestamp", "steps")
)
if incomplete:
logger.info("Incomplete initialization detected")
return True
return False
+169
View File
@@ -0,0 +1,169 @@
"""
Neo4j database initialization module for ClassroomCopilot
Handles database creation, schema setup, and calendar structure
"""
import os
import json
import time
from typing import Dict, Any, Optional
from datetime import datetime, timedelta
from modules.database.services.neo4j_service import Neo4jService
from modules.database.init.init_calendar import create_calendar
from modules.logger_tool import initialise_logger
logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
class Neo4jInitializer:
"""Handles Neo4j database initialization including database, schema, and calendar structure"""
def __init__(self):
self.neo4j_service = Neo4jService()
# Use lowercase to avoid case sensitivity issues
self.db_name = os.getenv("NEO4J_CC_DB", "classroomcopilot")
def initialize_database(self) -> Dict[str, Any]:
"""Initialize the single ClassroomCopilot database"""
logger.info(f"Initializing Neo4j database: {self.db_name}")
try:
# Create the main database
result = self.neo4j_service.create_database(self.db_name)
if result["status"] != "success":
return {
"success": False,
"message": f"Failed to create {self.db_name} database: {result['message']}"
}
logger.info(f"Successfully created {self.db_name} database")
# Wait for database to be fully available
logger.info("Waiting for database to be fully available...")
time.sleep(5) # Wait 5 seconds for database to be ready
# Verify database exists and is accessible
max_retries = 10
retry_delay = 2
for attempt in range(max_retries):
try:
# Try to check if the database exists to verify it's ready
test_result = self.neo4j_service.check_database_exists(self.db_name)
if test_result["status"] == "success" and test_result["exists"]:
logger.info(f"Database {self.db_name} is ready and accessible")
break
else:
logger.info(f"Database not ready yet (attempt {attempt + 1}/{max_retries}), waiting {retry_delay}s...")
time.sleep(retry_delay)
except Exception as e:
if attempt < max_retries - 1:
logger.info(f"Database existence check failed (attempt {attempt + 1}/{max_retries}), waiting {retry_delay}s...")
time.sleep(retry_delay)
else:
logger.warning(f"Database existence check failed after {max_retries} attempts: {str(e)}")
# Continue anyway as the database was created successfully
return {
"success": True,
"message": f"Successfully created {self.db_name} database",
"result": result
}
except Exception as e:
logger.error(f"Error creating database: {str(e)}")
return {
"success": False,
"message": f"Error creating database: {str(e)}"
}
def initialize_schema(self) -> Dict[str, Any]:
"""Initialize Neo4j schema on the ClassroomCopilot database"""
logger.info(f"Initializing Neo4j schema on {self.db_name}...")
try:
result = self.neo4j_service.initialize_schema(self.db_name)
if result["status"] != "success":
return {
"success": False,
"message": f"Failed to initialize schema on {self.db_name}: {result['message']}"
}
logger.info(f"Successfully initialized schema on {self.db_name}")
return {
"success": True,
"message": f"Successfully initialized schema on {self.db_name}",
"result": result
}
except Exception as e:
logger.error(f"Error initializing schema: {str(e)}")
return {
"success": False,
"message": f"Error initializing schema: {str(e)}"
}
def initialize_calendar_structure(self) -> Dict[str, Any]:
"""Initialize the calendar structure with days, weeks, months, and years"""
logger.info("Initializing calendar structure...")
try:
# Create calendar structure for the next 1 year
start_date = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
end_date = start_date + timedelta(days=365) # 1 year
# Create calendar structure directly (no calendar node needed)
calendar_nodes = create_calendar(
db_name=self.db_name,
start_date=start_date,
end_date=end_date,
time_chunk_node_length=0,
storage_tools=None
)
if calendar_nodes:
logger.info("Calendar structure created successfully")
return {
"success": True,
"message": "Calendar structure created successfully",
"calendar_nodes": calendar_nodes
}
else:
return {
"success": False,
"message": "Failed to create calendar structure"
}
except Exception as e:
logger.error(f"Error creating calendar structure: {str(e)}")
return {
"success": False,
"message": f"Error creating calendar structure: {str(e)}"
}
def initialize_neo4j() -> Dict[str, Any]:
"""Initialize Neo4j database setup"""
logger.info("Starting Neo4j database initialization...")
initializer = Neo4jInitializer()
# 1. Create the single database
db_result = initializer.initialize_database()
if not db_result["success"]:
return db_result
# 2. Initialize schema
schema_result = initializer.initialize_schema()
if not schema_result["success"]:
return schema_result
# 3. Initialize calendar structure
calendar_result = initializer.initialize_calendar_structure()
if not calendar_result["success"]:
return calendar_result
logger.info("Neo4j database initialization completed successfully")
return {
"success": True,
"message": "Neo4j database initialization completed successfully",
"database": db_result,
"schema": schema_result,
"calendar": calendar_result
}