Initial commit

This commit is contained in:
2025-07-11 13:21:49 +00:00
commit 8a7ab3ac24
262 changed files with 28219 additions and 0 deletions
+390
View File
@@ -0,0 +1,390 @@
import axiosInstance from '../../axiosConfig';
import { formatEmailForDatabase } from './neoDBService';
import { CCUserNodeProps, CCTeacherNodeProps, CCCalendarNodeProps, CCUserTeacherTimetableNodeProps } from '../../utils/tldraw/cc-base/cc-graph/cc-graph-types';
import { NavigationNode, NodeContext } from '../../types/navigation';
import { TLBinding, TLShapeId } from '@tldraw/tldraw';
import { logger } from '../../debugConfig';
import { useNavigationStore } from '../../stores/navigationStore';
import { DatabaseNameService } from './databaseNameService';
// Dev configuration - only hardcoded value we need
const DEV_SCHOOL_UUID = 'kevlarai';
interface ShapeState {
parentId: TLShapeId | null;
isPageChild: boolean | null;
hasChildren: boolean | null;
bindings: TLBinding[] | null;
}
interface NodeResponse {
status: string;
nodes: {
userNode: CCUserNodeProps;
calendarNode: CCCalendarNodeProps;
teacherNode: CCTeacherNodeProps;
timetableNode: CCUserTeacherTimetableNodeProps;
};
}
interface NodeDataResponse {
__primarylabel__: string;
unique_id: string;
tldraw_snapshot: string;
created: string;
merged: string;
state: ShapeState | null;
defaultComponent: boolean | null;
user_name?: string;
user_email?: string;
user_type?: string;
user_id?: string;
worker_node_data?: string;
[key: string]: string | number | boolean | null | ShapeState | undefined;
}
interface DefaultNodeResponse {
status: string;
node: {
id: string;
tldraw_snapshot: string;
type: string;
label: string;
data: NodeDataResponse;
};
}
export interface ProcessedUserNodes {
privateUserNode: CCUserNodeProps;
connectedNodes: {
calendar?: CCCalendarNodeProps;
teacher?: CCTeacherNodeProps;
timetable?: CCUserTeacherTimetableNodeProps;
};
}
export interface CalendarStructureResponse {
status: string;
data: {
currentDay: string;
days: Record<string, {
id: string;
date: string;
title: string;
}>;
weeks: Record<string, {
id: string;
title: string;
days: { id: string }[];
startDate: string;
endDate: string;
}>;
months: Record<string, {
id: string;
title: string;
days: { id: string }[];
weeks: { id: string }[];
year: string;
month: string;
}>;
years: {
id: string;
title: string;
months: { id: string }[];
year: string;
}[];
};
}
export interface WorkerStructureResponse {
status: string;
data: {
timetables: Record<string, Array<{
id: string;
title: string;
type: string;
startTime: string;
endTime: string;
}>>;
classes: Record<string, Array<{
id: string;
title: string;
type: string;
}>>;
lessons: Record<string, Array<{
id: string;
title: string;
type: string;
}>>;
journals: Record<string, Array<{
id: string;
title: string;
}>>;
planners: Record<string, Array<{
id: string;
title: string;
}>>;
};
}
export class UserNeoDBService {
static async fetchUserNodesData(
email: string,
userDbName?: string,
workerDbName?: string
): Promise<ProcessedUserNodes | null> {
try {
if (!userDbName) {
logger.error('neo4j-service', '❌ Attempted to fetch nodes without database name');
return null;
}
const formattedEmail = formatEmailForDatabase(email);
const uniqueId = `User_${formattedEmail}`;
logger.debug('neo4j-service', '🔄 Fetching user nodes data', {
email,
formattedEmail,
userDbName,
workerDbName,
uniqueId
});
// First get the user node from profile context
const userNode = await this.getDefaultNode('profile', userDbName);
if (!userNode || !userNode.data) {
throw new Error('Failed to fetch user node or node data missing');
}
logger.debug('neo4j-service', '✅ Found user node', {
nodeId: userNode.id,
type: userNode.type,
hasData: !!userNode.data,
userDbName,
workerDbName
});
// Initialize result structure
const processedNodes: ProcessedUserNodes = {
privateUserNode: {
...userNode.data,
__primarylabel__: 'User' as const,
title: userNode.data.user_email || 'User',
w: 200,
h: 200,
headerColor: '#3e6589',
backgroundColor: '#f0f0f0',
isLocked: false
} as CCUserNodeProps,
connectedNodes: {}
};
try {
// Get calendar node from calendar context
const calendarNode = await this.getDefaultNode('calendar', userDbName);
if (calendarNode?.data) {
processedNodes.connectedNodes.calendar = {
...calendarNode.data,
__primarylabel__: 'Calendar' as const,
title: calendarNode.data.calendar_name || 'Calendar',
w: 200,
h: 200,
headerColor: '#3e6589',
backgroundColor: '#f0f0f0',
isLocked: false
} as CCCalendarNodeProps;
logger.debug('neo4j-service', '✅ Found calendar node', {
nodeId: calendarNode.id,
tldraw_snapshot: calendarNode.data.tldraw_snapshot
});
} else {
logger.debug('neo4j-service', '️ No calendar node found');
}
} catch (error) {
logger.warn('neo4j-service', '⚠️ Failed to fetch calendar node:', error);
// Continue without calendar node
}
// Get teacher node from teaching context if worker database is available
if (workerDbName) {
try {
const teacherNode = await this.getDefaultNode('teaching', userDbName);
if (teacherNode?.data) {
processedNodes.connectedNodes.teacher = {
...teacherNode.data,
__primarylabel__: 'Teacher' as const,
title: teacherNode.data.teacher_name_formal || 'Teacher',
w: 200,
h: 200,
headerColor: '#3e6589',
backgroundColor: '#f0f0f0',
isLocked: false,
user_db_name: userDbName,
school_db_name: workerDbName
} as CCTeacherNodeProps;
logger.debug('neo4j-service', '✅ Found teacher node', {
nodeId: teacherNode.id,
tldraw_snapshot: teacherNode.data.tldraw_snapshot,
userDbName,
workerDbName
});
} else {
logger.debug('neo4j-service', '️ No teacher node found');
}
} catch (error) {
logger.warn('neo4j-service', '⚠️ Failed to fetch teacher node:', error);
// Continue without teacher node
}
}
logger.debug('neo4j-service', '✅ Processed all user nodes', {
hasUserNode: !!processedNodes.privateUserNode,
hasCalendar: !!processedNodes.connectedNodes.calendar,
hasTeacher: !!processedNodes.connectedNodes.teacher,
teacherData: processedNodes.connectedNodes.teacher ? {
unique_id: processedNodes.connectedNodes.teacher.unique_id,
school_db_name: processedNodes.connectedNodes.teacher.school_db_name,
tldraw_snapshot: processedNodes.connectedNodes.teacher.tldraw_snapshot
} : null
});
return processedNodes;
} catch (error: unknown) {
if (error instanceof Error) {
logger.error('neo4j-service', '❌ Failed to fetch user nodes:', error.message);
} else {
logger.error('neo4j-service', '❌ Failed to fetch user nodes:', String(error));
}
throw error;
}
}
static getUserDatabaseName(userType: string, username: string): string {
return DatabaseNameService.getUserPrivateDB(userType, username);
}
static getSchoolDatabaseName(schoolId: string): string {
return DatabaseNameService.getSchoolPrivateDB(schoolId);
}
static getDefaultSchoolDatabaseName(): string {
return DatabaseNameService.getDevelopmentSchoolDB();
}
static async fetchNodeData(nodeId: string, dbName: string): Promise<{ node_type: string; node_data: NodeResponse['nodes']['userNode'] } | null> {
try {
logger.debug('neo4j-service', '🔄 Fetching node data', { nodeId, dbName });
const response = await axiosInstance.get<{
status: string;
node: {
node_type: string;
node_data: NodeResponse['nodes']['userNode'];
};
}>('/database/tools/get-node', {
params: {
unique_id: nodeId,
db_name: dbName
}
});
if (response.data?.status === 'success' && response.data.node) {
return response.data.node;
}
return null;
} catch (error) {
logger.error('neo4j-service', '❌ Failed to fetch node data:', error);
throw error;
}
}
static getNodeDatabaseName(node: NavigationNode): string {
// If the node path starts with /node_filesystem/users/, it's in a user database
if (node.tldraw_snapshot.startsWith('/node_filesystem/users/')) {
const parts = node.tldraw_snapshot.split('/');
// parts[3] should be the database name (e.g., cc.users.surfacedashdev3atkevlaraidotcom)
return parts[3];
}
// For school/worker nodes, extract from the path or use a default
if (node.tldraw_snapshot.includes('/schools/')) {
return `cc.institutes.${DEV_SCHOOL_UUID}`;
}
// Default to user database if we can't determine
return node.tldraw_snapshot.split('/')[3];
}
static async getDefaultNode(context: NodeContext, dbName: string): Promise<NavigationNode | null> {
try {
logger.debug('neo4j-service', '🔄 Fetching default node', { context, dbName });
// For overview context, we need to extract the base context from the current navigation state
const params: Record<string, string> = { db_name: dbName };
if (context === 'overview') {
// Get the current base context from the navigation store
const navigationStore = useNavigationStore.getState();
params.base_context = navigationStore.context.base;
}
const response = await axiosInstance.get<DefaultNodeResponse>(
`/database/tools/get-default-node/${context}`,
{ params }
);
if (response.data?.status === 'success' && response.data.node) {
return {
id: response.data.node.id,
tldraw_snapshot: response.data.node.tldraw_snapshot,
type: response.data.node.type,
label: response.data.node.label,
data: response.data.node.data
};
}
return null;
} catch (error) {
logger.error('neo4j-service', '❌ Failed to fetch default node:', error);
throw error;
}
}
static async fetchCalendarStructure(dbName: string): Promise<CalendarStructureResponse['data']> {
try {
logger.debug('navigation', '🔄 Fetching calendar structure', { dbName });
const response = await axiosInstance.get<CalendarStructureResponse>(
`/database/calendar-structure/get-calendar-structure?db_name=${dbName}`
);
if (response.data.status === 'success') {
logger.info('navigation', '✅ Calendar structure fetched successfully');
return response.data.data;
}
throw new Error('Failed to fetch calendar structure');
} catch (error) {
logger.error('navigation', '❌ Failed to fetch calendar structure:', error);
throw error;
}
}
static async fetchWorkerStructure(dbName: string): Promise<WorkerStructureResponse['data']> {
try {
logger.debug('navigation', '🔄 Fetching worker structure', { dbName });
const response = await axiosInstance.get<WorkerStructureResponse>(
`/database/worker-structure/get-worker-structure?db_name=${dbName}`
);
if (response.data.status === 'success') {
logger.info('navigation', '✅ Worker structure fetched successfully');
return response.data.data;
}
throw new Error('Failed to fetch worker structure');
} catch (error) {
logger.error('navigation', '❌ Failed to fetch worker structure:', error);
throw error;
}
}
}