latest
This commit is contained in:
@@ -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'
|
||||
]
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user