277 lines
8.8 KiB
TypeScript
277 lines
8.8 KiB
TypeScript
/**
|
|
* Features API
|
|
*
|
|
* API-Schicht für das Multi-Tenant Feature-System.
|
|
* Hauptendpoint: GET /features/my - Lädt alle Mandate + Features + Instanzen + Permissions
|
|
*/
|
|
|
|
import api from '../api';
|
|
import type {
|
|
FeaturesMyResponse,
|
|
Mandate,
|
|
MandateFeature,
|
|
FeatureInstance,
|
|
InstancePermissions,
|
|
AccessLevel,
|
|
} from '../types/mandate';
|
|
import { mandateDisplayLabel } from '../utils/mandateDisplayUtils';
|
|
|
|
// =============================================================================
|
|
// MOCK DATA (Temporär bis Backend bereit)
|
|
// =============================================================================
|
|
|
|
const MOCK_PERMISSIONS: InstancePermissions = {
|
|
tables: {
|
|
TrusteeOrganisation: { view: true, read: 'g', create: 'g', update: 'g', delete: 'n' },
|
|
TrusteeContract: { view: true, read: 'g', create: 'g', update: 'm', delete: 'n' },
|
|
TrusteeDocument: { view: true, read: 'g', create: 'g', update: 'm', delete: 'm' },
|
|
TrusteePosition: { view: true, read: 'g', create: 'g', update: 'm', delete: 'n' },
|
|
},
|
|
views: {
|
|
'trustee-dashboard': true,
|
|
'trustee-organisations': true,
|
|
'trustee-contracts': true,
|
|
'trustee-documents': true,
|
|
'trustee-positions': true,
|
|
'trustee-roles': true,
|
|
'trustee-access': true,
|
|
},
|
|
};
|
|
|
|
const MOCK_CUSTOMER_PERMISSIONS: InstancePermissions = {
|
|
tables: {
|
|
TrusteeOrganisation: { view: true, read: 'm', create: 'n', update: 'n', delete: 'n' },
|
|
TrusteeContract: { view: true, read: 'm', create: 'n', update: 'n', delete: 'n' },
|
|
TrusteeDocument: { view: true, read: 'm', create: 'm', update: 'm', delete: 'n' },
|
|
TrusteePosition: { view: true, read: 'm', create: 'n', update: 'n', delete: 'n' },
|
|
},
|
|
views: {
|
|
'trustee-dashboard': true,
|
|
'trustee-contracts': true,
|
|
'trustee-documents': true,
|
|
'trustee-positions': true,
|
|
'trustee-organisations': false,
|
|
'trustee-roles': false,
|
|
'trustee-access': false,
|
|
},
|
|
};
|
|
|
|
const MOCK_WORKFLOW_PERMISSIONS: InstancePermissions = {
|
|
tables: {
|
|
WorkflowRun: { view: true, read: 'g', create: 'g', update: 'm', delete: 'n' },
|
|
WorkflowFile: { view: true, read: 'g', create: 'g', update: 'm', delete: 'm' },
|
|
},
|
|
views: {
|
|
'chatworkflow-dashboard': true,
|
|
'chatworkflow-runs': true,
|
|
'chatworkflow-files': true,
|
|
},
|
|
};
|
|
|
|
const MOCK_RESPONSE: FeaturesMyResponse = {
|
|
mandates: [
|
|
{
|
|
id: 'mand-soha',
|
|
name: 'soha-treuhand',
|
|
label: 'Soha Treuhand',
|
|
code: 'soha',
|
|
features: [
|
|
{
|
|
code: 'trustee',
|
|
label: 'Treuhand',
|
|
icon: 'briefcase',
|
|
instances: [
|
|
{
|
|
id: 'inst-soha-pamo',
|
|
featureCode: 'trustee',
|
|
mandateId: 'mand-soha',
|
|
mandateName: 'Soha Treuhand',
|
|
instanceLabel: 'PamoCreate AG',
|
|
userRoles: ['admin'],
|
|
permissions: MOCK_PERMISSIONS,
|
|
},
|
|
{
|
|
id: 'inst-soha-valueon',
|
|
featureCode: 'trustee',
|
|
mandateId: 'mand-soha',
|
|
mandateName: 'Soha Treuhand',
|
|
instanceLabel: 'ValueOn AG',
|
|
userRoles: ['customer'],
|
|
permissions: MOCK_CUSTOMER_PERMISSIONS,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: 'chatworkflow',
|
|
label: 'Workflow',
|
|
icon: 'play_circle',
|
|
instances: [
|
|
{
|
|
id: 'inst-soha-workflow',
|
|
featureCode: 'chatworkflow',
|
|
mandateId: 'mand-soha',
|
|
mandateName: 'Soha Treuhand',
|
|
instanceLabel: 'Beratung Dynamic',
|
|
userRoles: ['user'],
|
|
permissions: MOCK_WORKFLOW_PERMISSIONS,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: 'mand-swiss',
|
|
name: 'swisstreu',
|
|
label: 'SwissTreu',
|
|
code: 'swisstreu',
|
|
features: [
|
|
{
|
|
code: 'trustee',
|
|
label: 'Treuhand',
|
|
icon: 'briefcase',
|
|
instances: [
|
|
{
|
|
id: 'inst-swiss-firma-x',
|
|
featureCode: 'trustee',
|
|
mandateId: 'mand-swiss',
|
|
mandateName: 'SwissTreu',
|
|
instanceLabel: 'Firma X',
|
|
userRoles: ['customer'],
|
|
permissions: MOCK_CUSTOMER_PERMISSIONS,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
|
|
// Flag für Mock-Modus (auf false setzen wenn Backend bereit)
|
|
const USE_MOCK = false;
|
|
|
|
// =============================================================================
|
|
// API FUNCTIONS
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Lädt alle Mandate + Features + Instanzen + Permissions für den aktuellen User
|
|
*
|
|
* Endpoint: GET /api/features/my
|
|
*
|
|
* Response enthält:
|
|
* - Alle Mandanten zu denen der User Zugriff hat
|
|
* - Pro Mandant: Alle Features mit deren Instanzen
|
|
* - Pro Instanz: Summarische Berechtigungen (tables, views)
|
|
*/
|
|
export async function fetchMyFeatures(): Promise<FeaturesMyResponse> {
|
|
if (USE_MOCK) {
|
|
console.log('📦 featuresApi: Using MOCK data');
|
|
// Simuliere Netzwerk-Latenz
|
|
await new Promise(resolve => setTimeout(resolve, 300));
|
|
return MOCK_RESPONSE;
|
|
}
|
|
|
|
try {
|
|
console.log('📡 featuresApi: Fetching /api/features/my');
|
|
const response = await api.get<FeaturesMyResponse>('/api/features/my');
|
|
|
|
// Get the actual data (response.data contains the FeaturesMyResponse)
|
|
const data = response.data;
|
|
|
|
// DEBUG: Log all chatbot instances and their permissions
|
|
console.log('🔍 [DEBUG] featuresApi: Full response received', {
|
|
response,
|
|
data,
|
|
hasMandates: !!data?.mandates,
|
|
mandateCount: data?.mandates?.length || 0,
|
|
});
|
|
|
|
if (data?.mandates) {
|
|
data.mandates.forEach(mandate => {
|
|
mandate.features.forEach(feature => {
|
|
if (feature.code === 'chatbot') {
|
|
console.log('🔍 [DEBUG] featuresApi: Found chatbot feature', {
|
|
mandateId: mandate.id,
|
|
mandateName: mandateDisplayLabel(mandate),
|
|
featureCode: feature.code,
|
|
instanceCount: feature.instances.length,
|
|
});
|
|
feature.instances.forEach(instance => {
|
|
console.log('🔍 [DEBUG] featuresApi: Chatbot Instance Details:', {
|
|
instanceId: instance.id,
|
|
instanceLabel: instance.instanceLabel,
|
|
featureCode: instance.featureCode,
|
|
userRoles: instance.userRoles,
|
|
permissions: instance.permissions,
|
|
views: instance.permissions?.views,
|
|
viewKeys: instance.permissions?.views ? Object.keys(instance.permissions.views) : [],
|
|
hasConversationsView: instance.permissions?.views?.['chatbot-conversations'] ||
|
|
instance.permissions?.views?.['ui.feature.chatbot.conversations'] ||
|
|
instance.permissions?.views?.['_all'],
|
|
});
|
|
});
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
console.log('✅ featuresApi: Loaded features:', {
|
|
mandateCount: data?.mandates?.length || 0,
|
|
totalInstances: data?.mandates
|
|
?.flatMap(m => m.features)
|
|
?.flatMap(f => f.instances)
|
|
?.length || 0,
|
|
});
|
|
return data;
|
|
} catch (error) {
|
|
console.error('❌ featuresApi: Error fetching features:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Lädt die verfügbaren Features (für Admin - Feature-Instanz erstellen)
|
|
*
|
|
* Endpoint: GET /api/features/available
|
|
*/
|
|
export async function fetchAvailableFeatures(): Promise<MandateFeature[]> {
|
|
if (USE_MOCK) {
|
|
return [
|
|
{ code: 'trustee', label: 'Treuhand', icon: 'briefcase', instances: [] },
|
|
{ code: 'chatworkflow', label: 'Workflow', icon: 'play_circle', instances: [] },
|
|
{ code: 'chatbot', label: 'Chatbot', icon: 'chat', instances: [] },
|
|
];
|
|
}
|
|
|
|
const response = await api.get<MandateFeature[]>('/api/features/available');
|
|
return response.data;
|
|
}
|
|
|
|
// =============================================================================
|
|
// TYPE GUARDS
|
|
// =============================================================================
|
|
|
|
export function isValidAccessLevel(value: string): value is AccessLevel {
|
|
return ['n', 'm', 'g', 'a'].includes(value);
|
|
}
|
|
|
|
export function isValidMandate(obj: unknown): obj is Mandate {
|
|
if (!obj || typeof obj !== 'object') return false;
|
|
const mandate = obj as Record<string, unknown>;
|
|
return (
|
|
typeof mandate.id === 'string' &&
|
|
typeof mandate.name === 'string' &&
|
|
Array.isArray(mandate.features)
|
|
);
|
|
}
|
|
|
|
export function isValidFeatureInstance(obj: unknown): obj is FeatureInstance {
|
|
if (!obj || typeof obj !== 'object') return false;
|
|
const instance = obj as Record<string, unknown>;
|
|
return (
|
|
typeof instance.id === 'string' &&
|
|
typeof instance.featureCode === 'string' &&
|
|
typeof instance.mandateId === 'string' &&
|
|
typeof instance.instanceLabel === 'string'
|
|
);
|
|
}
|