380 lines
19 KiB
Python
380 lines
19 KiB
Python
import os
|
|
from datetime import timedelta, datetime
|
|
from abc import ABC, abstractmethod
|
|
from typing import Dict, Optional, Any, Union
|
|
|
|
from modules.database.services.neo4j_service import Neo4jService
|
|
import modules.database.schemas.nodes.users as user_nodes
|
|
import modules.database.schemas.nodes.workers.workers as worker_nodes
|
|
import modules.database.init.init_calendar as init_calendar
|
|
import modules.database.schemas.relationships.entity_relationships as entity_relationships
|
|
import modules.database.tools.neontology_tools as neon
|
|
from modules.logger_tool import initialise_logger
|
|
logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
|
|
|
|
def create_and_check_db(db_name):
|
|
neo4j_service = Neo4jService()
|
|
neo4j_service.create_database(db_name)
|
|
database_status = neo4j_service.check_database_exists(db_name)
|
|
if not database_status['exists']:
|
|
raise ValueError(f"Database {db_name} not found")
|
|
return database_status
|
|
|
|
class UserCreator(ABC):
|
|
def __init__(self, user_id, cc_users_db_name, user_type, worker_type, user_email, worker_email, cc_username, user_name, worker_name, calendar_start_date, calendar_end_date):
|
|
cc_schools_db_name = "cc.institutes" # Fix the TODO
|
|
self.cc_users_db_name = cc_users_db_name
|
|
self.user_db_name = f"{cc_users_db_name}.{user_type}.{cc_username}"
|
|
self.worker_db_name = f"{cc_schools_db_name}.{user_type}.{cc_username}"
|
|
self.user_type = user_type
|
|
self.worker_type = worker_type
|
|
self.cc_username = cc_username
|
|
self.user_email = user_email
|
|
self.worker_email = worker_email
|
|
self.user_name = user_name
|
|
self.worker_name = worker_name
|
|
self.user_id = user_id
|
|
self.user_nodes: Dict[str, Optional[Any]] = {
|
|
'default_user_node': None,
|
|
'private_user_node': None,
|
|
'worker_node': None,
|
|
'calendar_node': None
|
|
}
|
|
if calendar_start_date and calendar_end_date:
|
|
self.calendar_start_date = calendar_start_date
|
|
self.calendar_end_date = calendar_end_date
|
|
else:
|
|
logger.warning("No calendar start and end date provided, using default values")
|
|
self.calendar_start_date = datetime.now().date()
|
|
self.calendar_end_date = (datetime.now() + timedelta(days=5)).date()
|
|
|
|
@abstractmethod
|
|
def create_user(self):
|
|
pass
|
|
|
|
def create_user_node(self, db_name: str):
|
|
logger.info(f"Module is creating {self.cc_users_db_name} user node for {self.user_type} user {self.cc_username}")
|
|
try:
|
|
user_node = self._create_user_node(db_name)
|
|
logger.debug(f"User node creation completed for {self.cc_users_db_name} user node for {self.user_type} user {self.cc_username}: {user_node.to_dict()}")
|
|
return user_node
|
|
except Exception as e:
|
|
logger.error(f"Error creating user node: {e}")
|
|
raise
|
|
|
|
def _create_user_node(self, db_name: str):
|
|
# Ensure Neontology is initialized
|
|
neon.init_neontology_connection()
|
|
|
|
user_node = user_nodes.UserNode(
|
|
unique_id=f"{self.user_id}",
|
|
tldraw_snapshot="",
|
|
cc_username=f"{self.cc_username}",
|
|
user_email=f"{self.user_email}",
|
|
user_name=f"{self.user_name}",
|
|
user_db_name=f"{self.user_db_name}",
|
|
user_type=f"{self.user_type}",
|
|
)
|
|
logger.debug(f"User node template created: {user_node.to_dict()}. Writing to database {db_name}")
|
|
neon.create_or_merge_neontology_node(node=user_node, database=db_name, operation='merge')
|
|
logger.info(f"User node created: {user_node.to_dict()}")
|
|
return user_node
|
|
|
|
def create_storage_bucket(self, bucket_id: str, bucket_name: str, access_token: Optional[str] = None) -> bool:
|
|
"""Create public and private storage buckets for the user using their access token or service role during initialization"""
|
|
logger.info(f"Creating storage buckets for user {self.cc_username}")
|
|
|
|
try:
|
|
from modules.database.supabase.utils.client import SupabaseServiceRoleClient, SupabaseAnonClient, CreateBucketOptions
|
|
|
|
# During initialization (no access token provided), use service role
|
|
if not access_token:
|
|
logger.info("Using service role client for bucket creation during initialization")
|
|
supabase = SupabaseServiceRoleClient()
|
|
else:
|
|
# For regular operations, use the user's access token
|
|
logger.info("Using user token for bucket creation")
|
|
supabase = SupabaseAnonClient.for_user(access_token)
|
|
|
|
# Create both public and private buckets
|
|
buckets = [
|
|
{
|
|
"id": f"{bucket_id}.public",
|
|
"options": CreateBucketOptions(
|
|
name=f"{bucket_name} - Public Files",
|
|
public=True,
|
|
file_size_limit=50 * 1024 * 1024, # 50MB
|
|
allowed_mime_types=[
|
|
'image/*', 'video/*', 'application/pdf',
|
|
'application/msword', 'application/vnd.openxmlformats-officedocument.*',
|
|
'text/plain', 'text/csv', 'application/json'
|
|
]
|
|
)
|
|
},
|
|
{
|
|
"id": f"{bucket_id}.private",
|
|
"options": CreateBucketOptions(
|
|
name=f"{bucket_name} - Private Files",
|
|
public=False,
|
|
file_size_limit=50 * 1024 * 1024, # 50MB
|
|
allowed_mime_types=[
|
|
'image/*', 'video/*', 'application/pdf',
|
|
'application/msword', 'application/vnd.openxmlformats-officedocument.*',
|
|
'text/plain', 'text/csv', 'application/json'
|
|
]
|
|
)
|
|
}
|
|
]
|
|
|
|
success = True
|
|
for bucket in buckets:
|
|
try:
|
|
result = supabase.create_bucket(bucket["id"], bucket["options"])
|
|
if not result:
|
|
logger.error(f"Failed to create bucket {bucket['id']}")
|
|
success = False
|
|
else:
|
|
logger.info(f"Successfully created bucket {bucket['id']}")
|
|
except Exception as e:
|
|
logger.error(f"Error creating bucket {bucket['id']}: {str(e)}")
|
|
success = False
|
|
|
|
return success
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error creating storage buckets: {str(e)}")
|
|
return False
|
|
|
|
class SchoolUserCreator(UserCreator):
|
|
def __init__(self, user_id, cc_users_db_name, user_type, worker_type, user_email, worker_email, cc_username, user_name, worker_name, calendar_start_date, calendar_end_date, school_node, worker_node=None):
|
|
super().__init__(user_id, cc_users_db_name, user_type, worker_type, user_email, worker_email, cc_username, user_name, worker_name, calendar_start_date, calendar_end_date)
|
|
self.school_node = school_node
|
|
self.worker_node = worker_node
|
|
|
|
def create_user(self):
|
|
# Ensure Neontology is initialized
|
|
logger.debug(f"Initializing Neontology connection. Closing any existing connection")
|
|
neon.close_neontology_connection()
|
|
logger.debug(f"Neontology connection closed. Initializing new connection")
|
|
neon.init_neontology_connection()
|
|
|
|
if self.user_type in ['email_teacher', 'ms_teacher']:
|
|
worker_node = self.create_teacher_node()
|
|
elif self.user_type in ['email_student', 'ms_student']:
|
|
worker_node = self.create_student_node()
|
|
else:
|
|
raise ValueError(f"User type {self.user_type} not supported")
|
|
|
|
self.user_nodes[f'worker_node'] = worker_node
|
|
|
|
user_node = self.create_user_node(self.cc_users_db_name)
|
|
|
|
logger.info(f"User node created: {user_node}")
|
|
|
|
self.user_nodes['default_user_node'] = user_node
|
|
|
|
self.create_user_worker_relationship(user_node, worker_node)
|
|
|
|
self.create_worker_school_relationship(worker_node, self.school_node)
|
|
|
|
logger.info(f"Worker school relationship created between {worker_node} and {self.school_node}")
|
|
return self.user_nodes
|
|
|
|
def create_teacher_node(self):
|
|
logger.debug(f"Teacher node will be created for school: {self.school_node}")
|
|
try:
|
|
return self._create_teacher_node()
|
|
except KeyError as ke:
|
|
raise ValueError(f"Missing required key in worker_data: {ke}") from ke
|
|
except Exception as e:
|
|
raise ValueError(f"Error creating teacher node: {e}") from e
|
|
|
|
def _create_teacher_node(self):
|
|
teacher_node = worker_nodes.TeacherNode(
|
|
unique_id=f"{self.user_id}",
|
|
tldraw_snapshot="",
|
|
worker_name=self.worker_name,
|
|
worker_email=self.worker_email,
|
|
worker_db_name=self.worker_db_name,
|
|
worker_type=self.worker_type
|
|
)
|
|
# Use the school's private database name if available
|
|
school_db = self.school_node.private_database_name if hasattr(self.school_node, 'private_database_name') else f"cc.institutes.{self.school_node.school_type}.{self.school_node.id}"
|
|
logger.info(f"Teacher node template created: {teacher_node}... setting school db to {school_db}")
|
|
|
|
neon.create_or_merge_neontology_node(node=teacher_node, database=school_db, operation='merge')
|
|
|
|
logger.info(f"Teacher node merged into database {school_db}: {teacher_node}")
|
|
return teacher_node
|
|
|
|
def create_student_node(self):
|
|
student_node = worker_nodes.StudentNode(
|
|
unique_id=f"Student_{self.user_id}",
|
|
worker_name=self.worker_name,
|
|
worker_email=self.worker_email,
|
|
worker_db_name=self.worker_db_name,
|
|
worker_type=self.worker_type,
|
|
tldraw_snapshot=""
|
|
)
|
|
# Use the school's private database name if available
|
|
school_db = self.school_node.private_database_name if hasattr(self.school_node, 'private_database_name') else f"cc.institutes.{self.school_node.school_type}.{self.school_node.id}"
|
|
logger.info(f"Student node template created: {student_node}... setting school db to {school_db}")
|
|
|
|
neon.create_or_merge_neontology_node(node=student_node, database=school_db, operation='merge')
|
|
|
|
logger.info(f"Student node merged into database {school_db}: {student_node}")
|
|
return student_node
|
|
|
|
def create_user_worker_relationship(self, user_node, worker_node):
|
|
user_role_rel = entity_relationships.UserIsSchoolWorker(source=user_node, target=worker_node)
|
|
# Use the school's private database name if available
|
|
school_db = self.school_node.private_database_name if hasattr(self.school_node, 'private_database_name') else f"cc.institutes.{self.school_node.school_type}.{self.school_node.id}"
|
|
neon.create_or_merge_neontology_relationship(user_role_rel, database=school_db, operation='merge')
|
|
logger.info(f"Relationship created between user and worker in database {school_db}")
|
|
|
|
def create_worker_school_relationship(self, worker_node, school_node):
|
|
worker_school_rel = entity_relationships.EntityBelongsToSchool(source=worker_node, target=school_node)
|
|
# Use the school's private database name if available
|
|
school_db = school_node.private_database_name if hasattr(school_node, 'private_database_name') else f"cc.institutes.{school_node.school_type}.{school_node.id}"
|
|
neon.create_or_merge_neontology_relationship(worker_school_rel, database=school_db, operation='merge')
|
|
logger.info(f"Relationship created between worker and school in database {school_db}")
|
|
|
|
class NonSchoolUserCreator(UserCreator):
|
|
def __init__(self, user_id, cc_users_db_name, user_type, worker_type, user_email, worker_email, cc_username, user_name, worker_name, calendar_start_date, calendar_end_date, developer_role: str = "developer"):
|
|
super().__init__(user_id, cc_users_db_name, user_type, worker_type, user_email, worker_email, cc_username, user_name, worker_name, calendar_start_date, calendar_end_date)
|
|
self.developer_role = developer_role
|
|
|
|
def create_user(self, access_token: Optional[str] = None):
|
|
logger.debug(f"Creating user node for {self.user_type} user {self.cc_username} in database {self.cc_users_db_name}")
|
|
|
|
# Create storage buckets for the user
|
|
user_bucket_id = self.user_db_name
|
|
user_bucket_name = f"{self.user_type.title()} User Files - {self.user_name}"
|
|
if not self.create_storage_bucket(user_bucket_id, user_bucket_name, access_token=access_token):
|
|
logger.error(f"Failed to create storage bucket for user {self.cc_username}")
|
|
raise ValueError(f"Failed to create storage bucket for user {self.cc_username}")
|
|
|
|
# Create default user node first
|
|
default_user_node = self.create_user_node(self.cc_users_db_name)
|
|
logger.debug(f"Default user node created: {default_user_node}")
|
|
|
|
# Verify the return value of create_user_node
|
|
if default_user_node is None:
|
|
logger.error("Failed to create default user node. It is None.")
|
|
raise ValueError("Failed to create default user node. It is None.")
|
|
|
|
self.user_nodes[f'default_user_node'] = default_user_node
|
|
|
|
# Create the appropriate user db based on user_type
|
|
if self.user_type == 'admin':
|
|
logger.debug(f"Creating super admin db for {self.user_type} user {self.cc_username} in database {self.user_db_name}")
|
|
self.create_super_admin_db()
|
|
elif self.user_type == 'developer':
|
|
logger.debug(f"Creating developer db for {self.user_type} user {self.cc_username} in database {self.user_db_name}")
|
|
self.create_developer_db()
|
|
else:
|
|
raise ValueError(f"User type {self.user_type} not supported")
|
|
|
|
logger.debug(f"User nodes after creation: {self.user_nodes}")
|
|
return self.user_nodes
|
|
|
|
def create_super_admin_db(self):
|
|
logger.debug(f"Creating super admin db for {self.user_type} user {self.cc_username} in database {self.user_db_name}")
|
|
neon.init_neontology_connection()
|
|
|
|
# Create the user db self.user_db_name
|
|
create_and_check_db(self.user_db_name)
|
|
|
|
try:
|
|
# Create the user node again for the user db
|
|
logger.debug(f"Creating super admin user node for {self.user_type} user {self.cc_username} in database {self.user_db_name}")
|
|
private_user_node = self.create_user_node(self.user_db_name)
|
|
|
|
super_admin_node = worker_nodes.SuperAdminNode(
|
|
unique_id=f"SuperAdmin_{self.user_id}",
|
|
worker_email=self.worker_email,
|
|
tldraw_snapshot="",
|
|
worker_name=self.worker_name,
|
|
worker_db_name=self.worker_db_name,
|
|
worker_type=self.worker_type
|
|
)
|
|
logger.debug(f"Super admin node template created: {super_admin_node}. Writing to database {self.user_db_name}")
|
|
neon.create_or_merge_neontology_node(node=super_admin_node, database=self.user_db_name, operation='merge')
|
|
logger.info(f"Super admin node created: {super_admin_node}")
|
|
|
|
logger.debug(f"Creating relationship between user node: {private_user_node} and worker node: {super_admin_node}")
|
|
self.create_user_specific_relationship(user_node=private_user_node, worker_node=super_admin_node)
|
|
|
|
logger.debug(f"Creating calendar for {self.user_type} user {self.cc_username} in database {self.user_db_name}")
|
|
calendar_nodes = self.create_calendar(user_node=private_user_node)
|
|
logger.info(f"Super admin calendar created.")
|
|
|
|
self.user_nodes['private_user_node'] = private_user_node
|
|
self.user_nodes['worker_node'] = super_admin_node
|
|
|
|
logger.info(f"Returning user nodes: {self.user_nodes}")
|
|
|
|
return self.user_nodes
|
|
except Exception as e:
|
|
logger.error(f"Error creating super admin node: {e}")
|
|
raise ValueError(f"Error creating super admin node: {e}") from e
|
|
|
|
def create_developer_db(self):
|
|
neon.init_neontology_connection()
|
|
|
|
# Create the user db self.user_db_name
|
|
create_and_check_db(self.user_db_name)
|
|
|
|
try:
|
|
# Create the user node again for the user db
|
|
private_user_node = self.create_user_node(self.user_db_name)
|
|
|
|
developer_node = worker_nodes.DeveloperNode(
|
|
unique_id=f"Developer_{self.user_id}",
|
|
worker_name=self.worker_name,
|
|
worker_email=self.worker_email,
|
|
tldraw_snapshot="",
|
|
worker_db_name=self.worker_db_name,
|
|
worker_type=self.worker_type,
|
|
developer_role=self.developer_role
|
|
)
|
|
|
|
neon.create_or_merge_neontology_node(developer_node, database=self.user_db_name, operation='merge')
|
|
logger.info(f"Developer node created: {developer_node}")
|
|
|
|
self.user_nodes['private_user_node'] = private_user_node
|
|
self.user_nodes['worker_node'] = developer_node
|
|
|
|
self.create_user_specific_relationship(user_node=private_user_node, worker_node=developer_node)
|
|
|
|
return self.user_nodes
|
|
except Exception as e:
|
|
raise ValueError(f"Error creating developer node: {e}") from e
|
|
|
|
def create_user_specific_relationship(self, user_node: user_nodes.UserNode, worker_node: Union[worker_nodes.SuperAdminNode, worker_nodes.DeveloperNode]):
|
|
if user_node is None or worker_node is None:
|
|
logger.error("User node or worker node is None. Cannot create relationship.")
|
|
raise ValueError("User node or worker node is None. Cannot create relationship.")
|
|
|
|
logger.info(f"Creating relationship between user node: {user_node} and worker node: {worker_node}")
|
|
|
|
# Log the state of user_node and worker_node
|
|
logger.debug(f"user_node: {user_node}")
|
|
logger.debug(f"worker_node: {worker_node}")
|
|
|
|
if worker_node.worker_type == 'developer':
|
|
specific_user_rel = entity_relationships.UserIsSystemWorker(source=user_node, target=worker_node)
|
|
elif worker_node.worker_type == 'superadmin':
|
|
specific_user_rel = entity_relationships.UserIsSystemWorker(source=user_node, target=worker_node)
|
|
else:
|
|
raise ValueError(f"User type {worker_node.worker_type} not supported")
|
|
|
|
neon.create_or_merge_neontology_relationship(specific_user_rel, database=self.user_db_name, operation='merge')
|
|
logger.info("Relationship created between user and specific node")
|
|
|
|
def create_calendar(self, user_node: user_nodes.UserNode):
|
|
calendar_nodes = init_calendar.create_calendar(self.user_db_name, self.calendar_start_date, self.calendar_end_date, attach_to_calendar_node=True, owner_node=user_node)
|
|
|
|
logger.info(f"Calendar nodes created.")
|
|
return calendar_nodes
|