Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion apps/web/src/api/services/database-health.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
// Database health check service
const { SimpleSQLiteService } = require('./simple-sqlite.cjs');
let SimpleSQLiteService: any;

export class DatabaseHealthService {
private static service: any = null;

private static getService() {
if (!this.service) {
if (!SimpleSQLiteService) {
const { SimpleSQLiteService: ServiceClass } = require('./simple-sqlite.cjs');
SimpleSQLiteService = ServiceClass;
}
this.service = new SimpleSQLiteService();
}
return this.service;
Expand Down
25 changes: 17 additions & 8 deletions apps/web/src/api/services/prisma-auth.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,51 @@
// Real auth service using SQLite directly
const { SimpleAuthService } = require('./simple-auth.cjs');
let SimpleAuthService: any;
let authService: any;

// Initialize auth service
const authService = new SimpleAuthService();
// Lazy initialization to avoid SQLite loading during config phase
function getAuthService() {
if (!authService) {
const { SimpleAuthService: AuthClass } = require('./simple-auth.cjs');
authService = new AuthClass();
}
return authService;
}

export class PrismaAuthService {
/**
* Login with email and password
*/
static async login(email: string, password: string, rememberMe = false) {
return await authService.login(email, password, rememberMe);
return await getAuthService().login(email, password, rememberMe);
}

/**
* Refresh access token using refresh token
*/
static async refreshToken(refreshToken: string) {
return await authService.refreshToken(refreshToken);
return await getAuthService().refreshToken(refreshToken);
}

/**
* Logout and revoke refresh token
*/
static async logout(refreshToken: string) {
return await authService.logout(refreshToken);
return await getAuthService().logout(refreshToken);
}

/**
* Get user info from access token
*/
static getUserFromToken(token: string) {
return authService.getUserFromToken(token);
return getAuthService().getUserFromToken(token);
}

/**
* Close SQLite connection
*/
static async disconnect() {
await authService.disconnect();
if (authService) {
await authService.disconnect();
}
}
}
27 changes: 18 additions & 9 deletions apps/web/src/api/services/prisma-data.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,43 @@
// Real data service that uses SQLite directly
const { SimpleSQLiteService } = require('./simple-sqlite.cjs');
let SimpleSQLiteService: any;
let sqliteService: any;

// Initialize service
const sqliteService = new SimpleSQLiteService();
// Lazy initialization to avoid SQLite loading during config phase
function getDataService() {
if (!sqliteService) {
const { SimpleSQLiteService: DataClass } = require('./simple-sqlite.cjs');
sqliteService = new DataClass();
}
return sqliteService;
}

export class PrismaDataService {
/**
* Get organization summary KPIs
*/
static async getOrgSummary(orgId: string) {
return await sqliteService.getOrgSummary(orgId);
return await getDataService().getOrgSummary(orgId);
}

/**
* Get clients overview for organization
*/
static async getClientsOverview(orgId: string) {
return await sqliteService.getClientsOverview(orgId);
return await getDataService().getClientsOverview(orgId);
}

/**
* Get client summary with KPIs and insights
*/
static async getClientSummary(clientId: string, orgId: string) {
return await sqliteService.getClientSummary(clientId, orgId);
return await getDataService().getClientSummary(clientId, orgId);
}

/**
* Get client calls with pagination
*/
static async getClientCalls(clientId: string, orgId: string, limit = 10) {
return await sqliteService.getClientCalls(clientId, orgId, limit);
return await getDataService().getClientCalls(clientId, orgId, limit);
}

/**
Expand All @@ -41,13 +48,15 @@ export class PrismaDataService {
orgId: string,
status?: 'open' | 'done'
) {
return await sqliteService.getClientActionItems(clientId, orgId, status);
return await getDataService().getClientActionItems(clientId, orgId, status);
}

/**
* Close SQLite connection
*/
static async disconnect() {
await sqliteService.disconnect();
if (sqliteService) {
await sqliteService.disconnect();
}
}
}
77 changes: 69 additions & 8 deletions apps/web/src/auth/AuthService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import type {
} from './types';
import { getAuthItem, setAuthItem, removeAuthItem, clearAuthData } from '../utils/storage';

// Development mode flag
const isDevelopment = import.meta.env.DEV;
const MOCK_AUTH = isDevelopment && !import.meta.env.VITE_USE_REAL_AUTH;

// HTTP client for API calls
class ApiClient {
private baseUrl = ''; // Same origin
Expand Down Expand Up @@ -92,12 +96,55 @@ class AuthService {
await new Promise(resolve => setTimeout(resolve, ms));
}

/**
* Create a mock session for development
*/
private createMockSession(): AuthSession {
return {
user: {
id: 'mock-user-1',
email: 'demo@mudul.com',
name: 'Demo User',
createdAt: '2024-01-01T00:00:00Z',
lastLoginAt: new Date().toISOString()
},
organization: {
id: 'acme',
name: 'Acme Sales Org',
planTier: 'pro',
createdAt: '2024-01-01T00:00:00Z'
},
membership: {
userId: 'mock-user-1',
orgId: 'acme',
role: 'owner',
createdAt: '2024-01-01T00:00:00Z'
},
accessToken: 'mock-access-token',
refreshToken: 'mock-refresh-token',
expiresAt: this.calculateExpiryTime(60 * 24) // 24 hours from now
};
}

/**
* Authenticate user with email/password
*/
async login(credentials: LoginCredentials): Promise<AuthResponse> {
await this.simulateDelay();

// In development mode with mock auth, always succeed
if (MOCK_AUTH) {
console.log('πŸ”§ Using mock authentication for development');
const session = this.createMockSession();
this.currentSession = session;
this.storeSession(session);

return {
session,
isFirstLogin: false
};
}

try {
const result = await this.apiClient.post('/api/auth/login', credentials);

Expand Down Expand Up @@ -159,6 +206,17 @@ class AuthService {
throw this.createError('no_refresh_token', 'No valid refresh token available');
}

// In development mode with mock auth, just return updated session
if (MOCK_AUTH) {
const newSession: AuthSession = {
...session,
expiresAt: this.calculateExpiryTime(60 * 24) // 24 hours from now
};
this.currentSession = newSession;
this.storeSession(newSession);
return newSession;
}

try {
const result = await this.apiClient.post('/api/auth/refresh', {
refreshToken: session.refreshToken
Expand Down Expand Up @@ -195,15 +253,18 @@ class AuthService {
async logout(): Promise<void> {
const session = this.getCurrentSession();

try {
if (session?.refreshToken) {
await this.apiClient.post('/api/auth/logout', {
refreshToken: session.refreshToken
});
// In development mode, skip API call
if (!MOCK_AUTH) {
try {
if (session?.refreshToken) {
await this.apiClient.post('/api/auth/logout', {
refreshToken: session.refreshToken
});
}
} catch (error) {
console.warn('Logout API call failed:', error);
// Continue with local logout even if server call fails
}
} catch (error) {
console.warn('Logout API call failed:', error);
// Continue with local logout even if server call fails
}

// Clear local session
Expand Down
12 changes: 12 additions & 0 deletions apps/web/src/core/adapters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@ export const Adapters: AdapterMap = {
const clientCalls = ctx?.listCallsByClient?.(nodeId) ?? [];
const allActions: Array<{ text: string; due: string | null; owner: string; source: string }> = [];

// Add action items from calls
clientCalls.forEach(callNode => {
const callDetail = ctx?.getCallByNode?.(callNode.id);
if (callDetail?.actionItems) {
Expand All @@ -378,6 +379,17 @@ export const Adapters: AdapterMap = {
}
});

// Add standalone action items
const standaloneActions = ctx?.getStandaloneActionItems?.(nodeId) ?? [];
standaloneActions.forEach(action => {
allActions.push({
text: action.text,
due: action.dueDate,
owner: action.owner,
source: "Manual Entry"
});
});

// Sort by due date, prioritizing items with due dates
const sortedActions = allActions.sort((a, b) => {
if (!a.due && !b.due) return 0;
Expand Down
7 changes: 7 additions & 0 deletions apps/web/src/core/adapters/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ export type AdapterCtx = {
getAllCalls?: () => NodeBase[];
getCallByNode?: (nodeId: string) => SalesCallMinimal | null;
listCallsByClient?: (clientId: string) => NodeBase[];
getStandaloneActionItems?: (clientId: string) => Array<{
id: string;
owner: string;
text: string;
dueDate: string | null;
status: 'open' | 'done';
}>;
};

// Base adapter interface
Expand Down
Loading