Initial commit
This commit is contained in:
@@ -0,0 +1,265 @@
|
||||
import { User, AuthChangeEvent, Session } from '@supabase/supabase-js';
|
||||
import { TLUserPreferences } from '@tldraw/tldraw';
|
||||
import { supabase } from '../../supabaseClient';
|
||||
import { storageService, StorageKeys } from './localStorageService';
|
||||
import { logger } from '../../debugConfig';
|
||||
import { DatabaseNameService } from '../graph/databaseNameService';
|
||||
|
||||
export interface CCUser {
|
||||
id: string;
|
||||
email?: string;
|
||||
user_type: string;
|
||||
username: string;
|
||||
display_name: string;
|
||||
user_db_name: string;
|
||||
school_db_name: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface CCUserMetadata {
|
||||
username?: string;
|
||||
user_type?: string;
|
||||
display_name?: string;
|
||||
email?: string;
|
||||
name?: string;
|
||||
preferred_username?: string;
|
||||
[key: string]: string | undefined;
|
||||
}
|
||||
|
||||
export function convertToCCUser(user: User, metadata: CCUserMetadata): CCUser {
|
||||
// Extract username from various possible sources
|
||||
const username = metadata.username ||
|
||||
metadata.preferred_username ||
|
||||
metadata.email?.split('@')[0] ||
|
||||
user.email?.split('@')[0] ||
|
||||
'user';
|
||||
|
||||
// Extract display name from various possible sources
|
||||
const displayName = metadata.display_name ||
|
||||
metadata.name ||
|
||||
metadata.preferred_username ||
|
||||
username;
|
||||
|
||||
// Default to student if no user type specified
|
||||
const userType = metadata.user_type || 'student';
|
||||
|
||||
const userDbName = DatabaseNameService.getUserPrivateDB(
|
||||
userType,
|
||||
username
|
||||
);
|
||||
const schoolDbName = DatabaseNameService.getDevelopmentSchoolDB();
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
user_type: userType,
|
||||
username: username,
|
||||
display_name: displayName,
|
||||
user_db_name: userDbName,
|
||||
school_db_name: schoolDbName,
|
||||
created_at: user.created_at,
|
||||
updated_at: user.updated_at,
|
||||
};
|
||||
}
|
||||
|
||||
export type UserRole =
|
||||
| 'email_teacher'
|
||||
| 'email_student'
|
||||
| 'cc_admin'
|
||||
| 'cc_developer'
|
||||
| 'super_admin';
|
||||
|
||||
// Login response
|
||||
export interface LoginResponse {
|
||||
user: CCUser | null;
|
||||
accessToken: string | null;
|
||||
userRole: string;
|
||||
message: string | null;
|
||||
}
|
||||
|
||||
// Session response
|
||||
export interface SessionResponse {
|
||||
user: CCUser | null;
|
||||
accessToken: string | null;
|
||||
message: string | null;
|
||||
}
|
||||
|
||||
// Registration response
|
||||
export interface RegistrationResponse extends LoginResponse {
|
||||
user: CCUser;
|
||||
accessToken: string | null;
|
||||
userRole: UserRole;
|
||||
message: string | null;
|
||||
}
|
||||
|
||||
export interface EmailCredentials {
|
||||
email: string;
|
||||
password: string;
|
||||
role: 'email_teacher' | 'email_student';
|
||||
}
|
||||
|
||||
export type AuthCredentials = EmailCredentials;
|
||||
|
||||
export const getTldrawPreferences = (user: CCUser): TLUserPreferences => {
|
||||
return {
|
||||
id: user.id,
|
||||
colorScheme: 'system',
|
||||
};
|
||||
};
|
||||
|
||||
class AuthService {
|
||||
private static instance: AuthService;
|
||||
|
||||
private constructor() {}
|
||||
|
||||
onAuthStateChange(
|
||||
callback: (event: AuthChangeEvent, session: Session | null) => void
|
||||
) {
|
||||
return supabase.auth.onAuthStateChange((event, session) => {
|
||||
logger.info('auth-service', '🔄 Auth state changed', {
|
||||
event,
|
||||
hasSession: !!session,
|
||||
userId: session?.user?.id,
|
||||
eventType: event,
|
||||
});
|
||||
|
||||
// Ensure we clear storage on signout
|
||||
if (event === 'SIGNED_OUT') {
|
||||
storageService.clearAll();
|
||||
}
|
||||
|
||||
callback(event, session);
|
||||
});
|
||||
}
|
||||
|
||||
static getInstance(): AuthService {
|
||||
if (!AuthService.instance) {
|
||||
AuthService.instance = new AuthService();
|
||||
}
|
||||
return AuthService.instance;
|
||||
}
|
||||
|
||||
async getCurrentSession(): Promise<SessionResponse> {
|
||||
try {
|
||||
const {
|
||||
data: { session },
|
||||
error,
|
||||
} = await supabase.auth.getSession();
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (!session) {
|
||||
return { user: null, accessToken: null, message: 'No active session' };
|
||||
}
|
||||
|
||||
return {
|
||||
user: convertToCCUser(
|
||||
session.user,
|
||||
session.user.user_metadata as CCUserMetadata
|
||||
),
|
||||
accessToken: session.access_token,
|
||||
message: 'Session retrieved',
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('auth-service', 'Failed to get current session:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getCurrentUser(): Promise<CCUser | null> {
|
||||
try {
|
||||
const {
|
||||
data: { user },
|
||||
error,
|
||||
} = await supabase.auth.getUser();
|
||||
if (error || !user) {
|
||||
return null;
|
||||
}
|
||||
return convertToCCUser(user, user.user_metadata as CCUserMetadata);
|
||||
} catch (error) {
|
||||
logger.error('auth-service', 'Failed to get current user:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async login({
|
||||
email,
|
||||
password,
|
||||
role,
|
||||
}: EmailCredentials): Promise<LoginResponse> {
|
||||
try {
|
||||
logger.info('auth-service', '🔄 Attempting login', {
|
||||
email,
|
||||
role,
|
||||
});
|
||||
|
||||
const { data, error } = await supabase.auth.signInWithPassword({
|
||||
email,
|
||||
password,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
logger.error('auth-service', '❌ Supabase auth error', {
|
||||
error: error.message,
|
||||
status: error.status,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (!data.session) {
|
||||
logger.error('auth-service', '❌ No session after login');
|
||||
throw new Error('No session after login');
|
||||
}
|
||||
|
||||
const ccUser = convertToCCUser(
|
||||
data.user,
|
||||
data.user.user_metadata as CCUserMetadata
|
||||
);
|
||||
|
||||
// Store auth session in storage
|
||||
storageService.set(StorageKeys.USER_ROLE, ccUser.user_type);
|
||||
storageService.set(StorageKeys.USER, ccUser);
|
||||
storageService.set(StorageKeys.SUPABASE_TOKEN, data.session.access_token);
|
||||
|
||||
logger.info('auth-service', '✅ Login successful', {
|
||||
userId: ccUser.id,
|
||||
role: ccUser.user_type,
|
||||
username: ccUser.username,
|
||||
});
|
||||
|
||||
return {
|
||||
user: ccUser,
|
||||
accessToken: data.session.access_token,
|
||||
userRole: ccUser.user_type,
|
||||
message: 'Login successful',
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('auth-service', '❌ Login failed:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async logout(): Promise<void> {
|
||||
try {
|
||||
logger.debug('auth-service', '🔄 Attempting logout');
|
||||
const { error } = await supabase.auth.signOut({ scope: 'local' });
|
||||
if (error) {
|
||||
logger.error('auth-service', '❌ Logout failed:', error);
|
||||
throw error;
|
||||
}
|
||||
// Clear all stored data
|
||||
storageService.clearAll();
|
||||
// Force a refresh of the auth state
|
||||
await supabase.auth.refreshSession();
|
||||
logger.debug('auth-service', '✅ Logout successful');
|
||||
} catch (error) {
|
||||
logger.error('auth-service', '❌ Logout failed:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const authService = AuthService.getInstance();
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
import React from 'react';
|
||||
import { TLUserPreferences, TLUser } from '@tldraw/tldraw';
|
||||
import { CCUser } from '../../services/auth/authService';
|
||||
import { logger } from '../../debugConfig';
|
||||
|
||||
// Type-safe storage keys
|
||||
export enum StorageKeys {
|
||||
USER = 'user',
|
||||
USER_ROLE = 'user_role',
|
||||
SUPABASE_TOKEN = 'supabase_token',
|
||||
MS_TOKEN = 'msAccessToken',
|
||||
NEO4J_USER_DB = 'neo4jUserDbName',
|
||||
NEO4J_WORKER_DB = 'neo4jWorkerDbName',
|
||||
USER_NODES = 'userNodes',
|
||||
CALENDAR_DATA = 'calendarData',
|
||||
IS_NEW_REGISTRATION = 'isNewRegistration',
|
||||
TLDRAW_PREFERENCES = 'tldrawUserPreferences',
|
||||
TLDRAW_FILE_PATH = 'tldrawUserFilePath',
|
||||
LOCAL_SNAPSHOT = 'localSnapshot',
|
||||
NODE_FILE_PATH = 'nodeFilePath',
|
||||
ONENOTE_NOTEBOOK = 'oneNoteNotebook',
|
||||
PRESENTATION_MODE = 'presentationMode',
|
||||
TLDRAW_USER = 'tldrawUser'
|
||||
}
|
||||
|
||||
interface StorageValueTypes {
|
||||
[StorageKeys.USER]: CCUser;
|
||||
[StorageKeys.USER_ROLE]: string;
|
||||
[StorageKeys.SUPABASE_TOKEN]: string;
|
||||
[StorageKeys.MS_TOKEN]: string;
|
||||
[StorageKeys.NEO4J_USER_DB]: string;
|
||||
[StorageKeys.NEO4J_WORKER_DB]: string;
|
||||
[StorageKeys.USER_NODES]: any[];
|
||||
[StorageKeys.CALENDAR_DATA]: any;
|
||||
[StorageKeys.IS_NEW_REGISTRATION]: boolean;
|
||||
[StorageKeys.TLDRAW_PREFERENCES]: TLUserPreferences;
|
||||
[StorageKeys.TLDRAW_FILE_PATH]: string;
|
||||
[StorageKeys.LOCAL_SNAPSHOT]: any;
|
||||
[StorageKeys.NODE_FILE_PATH]: string;
|
||||
[StorageKeys.ONENOTE_NOTEBOOK]: any;
|
||||
[StorageKeys.PRESENTATION_MODE]: boolean;
|
||||
[StorageKeys.TLDRAW_USER]: TLUser;
|
||||
}
|
||||
|
||||
type StorageKey = keyof StorageValueTypes;
|
||||
|
||||
class StorageService {
|
||||
private static instance: StorageService;
|
||||
|
||||
private constructor() {}
|
||||
|
||||
static getInstance(): StorageService {
|
||||
if (!StorageService.instance) {
|
||||
StorageService.instance = new StorageService();
|
||||
}
|
||||
return StorageService.instance;
|
||||
}
|
||||
|
||||
get<K extends StorageKey>(key: K): StorageValueTypes[K] | null {
|
||||
try {
|
||||
const item = localStorage.getItem(key);
|
||||
return item ? JSON.parse(item) : null;
|
||||
} catch (error) {
|
||||
logger.error('storage-service', `Error retrieving ${key}:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
set<K extends StorageKey>(key: K, value: StorageValueTypes[K]): void {
|
||||
try {
|
||||
const serializedValue = JSON.stringify(value);
|
||||
localStorage.setItem(key, serializedValue);
|
||||
logger.debug('storage-service', `Stored ${key} in localStorage`);
|
||||
} catch (error) {
|
||||
logger.error('storage-service', `Error storing ${key}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
remove(key: StorageKey): void {
|
||||
try {
|
||||
localStorage.removeItem(key);
|
||||
logger.debug('storage-service', `Removed ${key} from localStorage`);
|
||||
} catch (error) {
|
||||
logger.error('storage-service', `Error removing ${key}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
clearAll(): void {
|
||||
try {
|
||||
// Only clear app-specific storage, not Supabase's internal keys
|
||||
Object.values(StorageKeys).forEach(key => {
|
||||
localStorage.removeItem(key);
|
||||
});
|
||||
logger.debug('storage-service', 'Cleared all app items from localStorage');
|
||||
} catch (error) {
|
||||
logger.error('storage-service', 'Error clearing storage:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper method to update state and storage together
|
||||
setStateAndStorage<K extends StorageKey>(
|
||||
setter: React.Dispatch<React.SetStateAction<StorageValueTypes[K]>>,
|
||||
key: K,
|
||||
value: StorageValueTypes[K]
|
||||
): void {
|
||||
setter(value);
|
||||
this.set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
export const storageService = StorageService.getInstance();
|
||||
@@ -0,0 +1,107 @@
|
||||
import { supabase } from '../../../supabaseClient';
|
||||
import axios from '../../../axiosConfig';
|
||||
|
||||
export interface StandardizedOneNoteDetails {
|
||||
id: string;
|
||||
displayName: string;
|
||||
createdDateTime: string;
|
||||
lastModifiedDateTime: string;
|
||||
links: {
|
||||
oneNoteClientUrl: string;
|
||||
oneNoteWebUrl: string;
|
||||
};
|
||||
}
|
||||
|
||||
export async function updateUserOneNoteDetails(userId: string, oneNoteDetails: StandardizedOneNoteDetails) {
|
||||
const { error } = await supabase
|
||||
.from('profiles')
|
||||
.update({
|
||||
one_note_details: oneNoteDetails,
|
||||
updated_at: new Date().toISOString()
|
||||
})
|
||||
.eq('id', userId);
|
||||
|
||||
if (error) {
|
||||
console.error('Error updating OneNote details:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getOneNoteNotebooks(msAccessToken: string) {
|
||||
try {
|
||||
const response = await axios.get(`/msgraph/onenote/get-onenote-notebooks`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${msAccessToken}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error getting notebooks:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function createOneNoteNotebook(msAccessToken: string, uid: string): Promise<StandardizedOneNoteDetails> {
|
||||
if (!msAccessToken) {
|
||||
throw new Error('Microsoft token not found');
|
||||
}
|
||||
|
||||
try {
|
||||
const notebooks = await getOneNoteNotebooks(msAccessToken);
|
||||
const existingNotebook = notebooks.value.find((notebook: any) =>
|
||||
notebook.displayName === 'Classroom Copilot'
|
||||
);
|
||||
|
||||
if (existingNotebook) {
|
||||
const standardizedNotebook = standardizeNotebookDetails(existingNotebook);
|
||||
await updateUserOneNoteDetails(uid, standardizedNotebook);
|
||||
return standardizedNotebook;
|
||||
}
|
||||
|
||||
const response = await axios.post(
|
||||
`/msgraph/onenote/create-onenote-notebook?notebook_name=${encodeURIComponent('Classroom Copilot')}`,
|
||||
{},
|
||||
{
|
||||
headers: {
|
||||
'Authorization': `Bearer ${msAccessToken}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (response.status !== 200) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const standardizedNotebook = standardizeNotebookDetails(response.data);
|
||||
await updateUserOneNoteDetails(uid, standardizedNotebook);
|
||||
return standardizedNotebook;
|
||||
} catch (error) {
|
||||
console.error('Error creating notebook:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function registerOneNoteUser(msAccessToken: string, uid: string) {
|
||||
try {
|
||||
return await createOneNoteNotebook(msAccessToken, uid);
|
||||
} catch (error) {
|
||||
console.error('Error registering Microsoft user:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function standardizeNotebookDetails(notebook: any): StandardizedOneNoteDetails {
|
||||
const notebookData = notebook.data || notebook;
|
||||
return {
|
||||
id: notebookData.id || '',
|
||||
displayName: notebookData.displayName || '',
|
||||
createdDateTime: notebookData.createdDateTime || '',
|
||||
lastModifiedDateTime: notebookData.lastModifiedDateTime || '',
|
||||
links: {
|
||||
oneNoteClientUrl: notebookData.links?.oneNoteClientUrl?.href || '',
|
||||
oneNoteWebUrl: notebookData.links?.oneNoteWebUrl?.href || '',
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import { TLUserPreferences } from '@tldraw/tldraw';
|
||||
import { supabase } from '../../supabaseClient';
|
||||
import { logger } from '../../debugConfig';
|
||||
import { CCUser } from './authService';
|
||||
|
||||
export type UserProfile = CCUser;
|
||||
|
||||
export interface UserProfileUpdate extends Partial<UserProfile> {
|
||||
id: string; // ID is always required for updates
|
||||
}
|
||||
|
||||
export interface UserPreferences {
|
||||
tldraw?: TLUserPreferences;
|
||||
theme?: 'light' | 'dark' | 'system';
|
||||
notifications?: boolean;
|
||||
}
|
||||
|
||||
export async function createUserProfile(profile: UserProfile): Promise<UserProfile | null> {
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('profiles')
|
||||
.insert([profile])
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
logger.error('supabase-profile-service', '❌ Failed to create user profile', {
|
||||
userId: profile.id,
|
||||
error
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
logger.error('supabase-profile-service', '❌ Error in createUserProfile', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getUserProfile(userId: string): Promise<UserProfile | null> {
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('profiles')
|
||||
.select('*')
|
||||
.eq('id', userId)
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
logger.error('supabase-profile-service', '❌ Failed to fetch user profile', {
|
||||
userId,
|
||||
error
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
logger.error('supabase-profile-service', '❌ Error in getUserProfile', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateUserProfile(update: UserProfileUpdate): Promise<UserProfile | null> {
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('profiles')
|
||||
.update(update)
|
||||
.eq('id', update.id)
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
logger.error('supabase-profile-service', '❌ Failed to update user profile', {
|
||||
userId: update.id,
|
||||
error
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
logger.error('supabase-profile-service', '❌ Error in updateUserProfile', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
import { supabase } from '../../supabaseClient';
|
||||
import { CCUser, convertToCCUser } from '../../services/auth/authService';
|
||||
import { EmailCredentials } from '../../services/auth/authService';
|
||||
import { formatEmailForDatabase } from '../graph/neoDBService';
|
||||
import { RegistrationResponse } from '../../services/auth/authService';
|
||||
import { neoRegistrationService } from '../graph/neoRegistrationService';
|
||||
import { storageService, StorageKeys } from './localStorageService';
|
||||
import { logger } from '../../debugConfig';
|
||||
|
||||
const REGISTRATION_SERVICE = 'registration-service';
|
||||
|
||||
export class RegistrationService {
|
||||
private static instance: RegistrationService;
|
||||
|
||||
private constructor() {}
|
||||
|
||||
static getInstance(): RegistrationService {
|
||||
if (!RegistrationService.instance) {
|
||||
RegistrationService.instance = new RegistrationService();
|
||||
}
|
||||
return RegistrationService.instance;
|
||||
}
|
||||
|
||||
async register(credentials: EmailCredentials, displayName: string): Promise<RegistrationResponse> {
|
||||
try {
|
||||
logger.debug(REGISTRATION_SERVICE, '🔄 Starting registration', {
|
||||
email: credentials.email,
|
||||
role: credentials.role,
|
||||
hasDisplayName: !!displayName
|
||||
});
|
||||
|
||||
// Generate username from email (or use another method)
|
||||
const username = formatEmailForDatabase(credentials.email);
|
||||
|
||||
// 1. First sign up the user in auth
|
||||
const { data: authData, error: signUpError } = await supabase.auth.signUp({
|
||||
email: credentials.email,
|
||||
password: credentials.password,
|
||||
options: {
|
||||
data: {
|
||||
user_type: credentials.role,
|
||||
username: username,
|
||||
display_name: displayName
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (signUpError) {
|
||||
logger.error(REGISTRATION_SERVICE, '❌ Supabase signup error', { error: signUpError });
|
||||
throw signUpError;
|
||||
}
|
||||
|
||||
if (!authData.user) {
|
||||
logger.error(REGISTRATION_SERVICE, '❌ No user data after registration');
|
||||
throw new Error('No user data after registration');
|
||||
}
|
||||
|
||||
const ccUser: CCUser = convertToCCUser(authData.user, authData.user.user_metadata);
|
||||
|
||||
// 2. Update the profile with the correct user type
|
||||
const { error: updateError } = await supabase
|
||||
.from('profiles')
|
||||
.update({
|
||||
user_type: credentials.role,
|
||||
username: username,
|
||||
display_name: displayName
|
||||
})
|
||||
.eq('id', authData.user.id)
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (updateError) {
|
||||
logger.error(REGISTRATION_SERVICE, '❌ Failed to update profile', updateError);
|
||||
throw updateError;
|
||||
}
|
||||
|
||||
storageService.set(StorageKeys.IS_NEW_REGISTRATION, true);
|
||||
|
||||
// 3. Create Neo4j nodes
|
||||
try {
|
||||
const userNode = await neoRegistrationService.registerNeo4JUser(
|
||||
ccUser,
|
||||
username, // Pass username for database operations
|
||||
credentials.role
|
||||
);
|
||||
|
||||
logger.info(REGISTRATION_SERVICE, '✅ Registration successful with Neo4j setup', {
|
||||
userId: ccUser.id,
|
||||
hasUserNode: !!userNode
|
||||
});
|
||||
|
||||
return {
|
||||
user: ccUser,
|
||||
accessToken: authData.session?.access_token || null,
|
||||
userRole: credentials.role,
|
||||
message: 'Registration successful'
|
||||
};
|
||||
} catch (neo4jError) {
|
||||
logger.warn(REGISTRATION_SERVICE, '⚠️ Neo4j setup problem', {
|
||||
userId: ccUser.id,
|
||||
error: neo4jError
|
||||
});
|
||||
// Return success even if Neo4j setup is pending
|
||||
return {
|
||||
user: ccUser,
|
||||
accessToken: authData.session?.access_token || null,
|
||||
userRole: credentials.role,
|
||||
message: 'Registration successful - Neo4j setup pending'
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(REGISTRATION_SERVICE, '❌ Registration failed:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const registrationService = RegistrationService.getInstance();
|
||||
Reference in New Issue
Block a user