API and persisted records use PowerOnModel system fields: - sysCreatedAt, sysCreatedBy, sysModifiedAt, sysModifiedBy Removed legacy JSON/DB field names: - _createdAt, _createdBy, _modifiedAt, _modifiedBy Frontend (frontend_nyla) and gateway call sites were updated accordingly. Database: - Bootstrap runs idempotent backfill (_migrateSystemFieldColumns) from old underscore columns and selected business duplicates into sys* where sys* IS NULL. - Re-run app bootstrap against each PostgreSQL database after deploy. - Optional: DROP INDEX IF EXISTS "idx_invitation_createdby" if an old index remains; new index: idx_invitation_syscreatedby on Invitation(sysCreatedBy). Tests: - RBAC integration tests aligned with current GROUP mandate filter and UserMandate-based UserConnection GROUP clause; buildRbacWhereClause(..., mandateId=...) must be passed explicitly (same as production request context).
385 lines
10 KiB
TypeScript
385 lines
10 KiB
TypeScript
import { ApiRequestOptions } from '../hooks/useApi';
|
|
|
|
// ============================================================================
|
|
// TYPES & INTERFACES
|
|
// ============================================================================
|
|
|
|
export interface Automation {
|
|
id: string;
|
|
mandateId: string;
|
|
featureInstanceId: string;
|
|
label: string;
|
|
template: string | object;
|
|
placeholders: Record<string, string>;
|
|
schedule: string;
|
|
active: boolean;
|
|
status?: string;
|
|
lastExecution?: number;
|
|
nextExecution?: number;
|
|
executionLogs?: AutomationLog[];
|
|
allowedProviders?: string[];
|
|
sysCreatedAt?: number;
|
|
_updatedAt?: number;
|
|
sysCreatedByUserName?: string;
|
|
mandateName?: string;
|
|
featureInstanceName?: string;
|
|
[key: string]: any;
|
|
}
|
|
|
|
export interface AutomationLog {
|
|
id: string;
|
|
timestamp: number;
|
|
status: string;
|
|
workflowId?: string;
|
|
messages?: string[];
|
|
}
|
|
|
|
// Multilingual text type (matches backend TextMultilingual)
|
|
export interface TextMultilingual {
|
|
en: string;
|
|
ge?: string;
|
|
fr?: string;
|
|
it?: string;
|
|
}
|
|
|
|
// AutomationTemplate from DB
|
|
export interface AutomationTemplate {
|
|
id: string;
|
|
label: TextMultilingual;
|
|
overview?: TextMultilingual;
|
|
template: string; // JSON string with {{KEY:...}} placeholders
|
|
sysCreatedAt?: number;
|
|
sysCreatedBy?: string;
|
|
sysCreatedByUserName?: string;
|
|
}
|
|
|
|
// Workflow action definition from backend
|
|
export interface WorkflowAction {
|
|
method: string;
|
|
action: string;
|
|
actionId: string;
|
|
description: string;
|
|
category?: string;
|
|
parameters: WorkflowActionParameter[];
|
|
exampleJson: {
|
|
execMethod: string;
|
|
execAction: string;
|
|
execParameters: Record<string, any>;
|
|
execResultLabel: string;
|
|
};
|
|
}
|
|
|
|
export interface WorkflowActionParameter {
|
|
name: string;
|
|
type: string;
|
|
frontendType: string;
|
|
required: boolean;
|
|
default?: any;
|
|
description: string;
|
|
frontendOptions?: string | string[];
|
|
}
|
|
|
|
export interface CreateAutomationRequest {
|
|
label: string;
|
|
template: string;
|
|
placeholders?: Record<string, string>;
|
|
schedule?: string;
|
|
active?: boolean;
|
|
mandateId?: string;
|
|
featureInstanceId?: string;
|
|
}
|
|
|
|
export interface UpdateAutomationRequest {
|
|
label?: string;
|
|
template?: string;
|
|
placeholders?: Record<string, string>;
|
|
schedule?: string;
|
|
active?: boolean;
|
|
}
|
|
|
|
export interface ExecuteAutomationResponse {
|
|
id: string;
|
|
status: string;
|
|
workflowId?: string;
|
|
[key: string]: any;
|
|
}
|
|
|
|
// Type for the request function passed to API functions
|
|
export type ApiRequestFunction = (options: ApiRequestOptions<any>) => Promise<any>;
|
|
|
|
// ============================================================================
|
|
// API REQUEST FUNCTIONS
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Fetch all automations for the current mandate
|
|
* Endpoint: GET /api/automations
|
|
*/
|
|
export async function fetchAutomations(request: ApiRequestFunction): Promise<Automation[]> {
|
|
console.log('📤 fetchAutomations: Making API request to /api/automations');
|
|
|
|
try {
|
|
const data = await request({
|
|
url: '/api/automations',
|
|
method: 'get'
|
|
});
|
|
|
|
console.log('📥 fetchAutomations: API response:', data);
|
|
|
|
// Handle different response formats
|
|
let automations: Automation[] = [];
|
|
|
|
if (Array.isArray(data)) {
|
|
automations = data;
|
|
} else if (data && typeof data === 'object') {
|
|
if (Array.isArray(data.automations)) {
|
|
automations = data.automations;
|
|
} else if (Array.isArray(data.items)) {
|
|
automations = data.items;
|
|
} else if (Array.isArray(data.data)) {
|
|
automations = data.data;
|
|
}
|
|
}
|
|
|
|
console.log(`✅ fetchAutomations: Returning ${automations.length} automations`);
|
|
return automations;
|
|
} catch (error) {
|
|
console.error('❌ fetchAutomations: Error fetching automations:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetch a single automation by ID
|
|
* Endpoint: GET /api/automations/{automationId}
|
|
*/
|
|
export async function fetchAutomation(
|
|
request: ApiRequestFunction,
|
|
automationId: string
|
|
): Promise<Automation> {
|
|
return await request({
|
|
url: `/api/automations/${automationId}`,
|
|
method: 'get'
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Create a new automation
|
|
* Endpoint: POST /api/automations
|
|
*/
|
|
export async function createAutomationApi(
|
|
request: ApiRequestFunction,
|
|
automationData: CreateAutomationRequest
|
|
): Promise<Automation> {
|
|
return await request({
|
|
url: '/api/automations',
|
|
method: 'post',
|
|
data: automationData
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Update an existing automation
|
|
* Endpoint: PUT /api/automations/{automationId}
|
|
*/
|
|
export async function updateAutomationApi(
|
|
request: ApiRequestFunction,
|
|
automationId: string,
|
|
updateData: UpdateAutomationRequest
|
|
): Promise<Automation> {
|
|
return await request({
|
|
url: `/api/automations/${automationId}`,
|
|
method: 'put',
|
|
data: updateData
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Delete an automation
|
|
* Endpoint: DELETE /api/automations/{automationId}
|
|
*/
|
|
export async function deleteAutomationApi(
|
|
request: ApiRequestFunction,
|
|
automationId: string
|
|
): Promise<void> {
|
|
await request({
|
|
url: `/api/automations/${automationId}`,
|
|
method: 'delete'
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Execute an automation (test mode)
|
|
* Endpoint: POST /api/automations/{automationId}/execute
|
|
*/
|
|
export async function executeAutomationApi(
|
|
request: ApiRequestFunction,
|
|
automationId: string
|
|
): Promise<ExecuteAutomationResponse> {
|
|
return await request({
|
|
url: `/api/automations/${automationId}/execute`,
|
|
method: 'post'
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Fetch automation attributes for dynamic form generation
|
|
* Endpoint: GET /api/attributes/AutomationDefinition
|
|
*/
|
|
export async function fetchAutomationAttributes(
|
|
request: ApiRequestFunction
|
|
): Promise<any[]> {
|
|
const data = await request({
|
|
url: '/api/attributes/AutomationDefinition',
|
|
method: 'get'
|
|
});
|
|
|
|
if (data?.attributes && Array.isArray(data.attributes)) {
|
|
return data.attributes;
|
|
}
|
|
|
|
if (Array.isArray(data)) {
|
|
return data;
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
// ============================================================================
|
|
// AUTOMATION TEMPLATES API
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Fetch all automation templates (RBAC-filtered: own templates)
|
|
* Endpoint: GET /api/automation-templates
|
|
*/
|
|
export async function fetchAutomationTemplates(
|
|
request: ApiRequestFunction,
|
|
params?: any
|
|
): Promise<any> {
|
|
const requestParams: Record<string, string> = {};
|
|
if (params && typeof params === 'object') {
|
|
const paginationObj: any = {};
|
|
if (params.page !== undefined) paginationObj.page = params.page;
|
|
if (params.pageSize !== undefined) paginationObj.pageSize = params.pageSize;
|
|
if (params.sort) paginationObj.sort = params.sort;
|
|
if (params.filters) paginationObj.filters = params.filters;
|
|
if (params.search) paginationObj.search = params.search;
|
|
if (Object.keys(paginationObj).length > 0) {
|
|
requestParams.pagination = JSON.stringify(paginationObj);
|
|
}
|
|
}
|
|
return await request({
|
|
url: '/api/automation-templates',
|
|
method: 'get',
|
|
params: requestParams,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Fetch single automation template by ID
|
|
* Endpoint: GET /api/automation-templates/{templateId}
|
|
*/
|
|
export async function fetchAutomationTemplateById(
|
|
request: ApiRequestFunction,
|
|
templateId: string
|
|
): Promise<AutomationTemplate | null> {
|
|
try {
|
|
return await request({
|
|
url: `/api/automation-templates/${templateId}`,
|
|
method: 'get'
|
|
});
|
|
} catch (error) {
|
|
console.error('Error fetching template:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create new automation template
|
|
* Endpoint: POST /api/automation-templates
|
|
*/
|
|
export async function createAutomationTemplateApi(
|
|
request: ApiRequestFunction,
|
|
templateData: Omit<AutomationTemplate, 'id' | 'sysCreatedAt' | 'sysCreatedBy'>
|
|
): Promise<AutomationTemplate> {
|
|
return await request({
|
|
url: '/api/automation-templates',
|
|
method: 'post',
|
|
data: templateData
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Update automation template
|
|
* Endpoint: PUT /api/automation-templates/{templateId}
|
|
*/
|
|
export async function updateAutomationTemplateApi(
|
|
request: ApiRequestFunction,
|
|
templateId: string,
|
|
templateData: Partial<AutomationTemplate>
|
|
): Promise<AutomationTemplate> {
|
|
return await request({
|
|
url: `/api/automation-templates/${templateId}`,
|
|
method: 'put',
|
|
data: templateData
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Delete automation template
|
|
* Endpoint: DELETE /api/automation-templates/{templateId}
|
|
*/
|
|
export async function deleteAutomationTemplateApi(
|
|
request: ApiRequestFunction,
|
|
templateId: string
|
|
): Promise<void> {
|
|
await request({
|
|
url: `/api/automation-templates/${templateId}`,
|
|
method: 'delete'
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Fetch automation template attributes for dynamic form generation
|
|
* Endpoint: GET /api/automation-templates/attributes
|
|
*/
|
|
export async function fetchAutomationTemplateAttributes(
|
|
request: ApiRequestFunction
|
|
): Promise<any[]> {
|
|
const data = await request({
|
|
url: '/api/automation-templates/attributes',
|
|
method: 'get'
|
|
});
|
|
|
|
// Backend returns: { attributes: { model: "...", attributes: [...] } }
|
|
if (data?.attributes?.attributes && Array.isArray(data.attributes.attributes)) {
|
|
return data.attributes.attributes;
|
|
}
|
|
|
|
// Fallback: direct attributes array
|
|
if (data?.attributes && Array.isArray(data.attributes)) {
|
|
return data.attributes;
|
|
}
|
|
|
|
return Array.isArray(data) ? data : [];
|
|
}
|
|
|
|
// ============================================================================
|
|
// WORKFLOW ACTIONS API
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Fetch available workflow actions (RBAC-filtered)
|
|
* Endpoint: GET /api/automations/actions
|
|
*/
|
|
export async function fetchWorkflowActions(
|
|
request: ApiRequestFunction
|
|
): Promise<WorkflowAction[]> {
|
|
const data = await request({
|
|
url: '/api/automations/actions',
|
|
method: 'get'
|
|
});
|
|
|
|
return data?.actions || [];
|
|
}
|