Files
mcp-wms-api/src/services/workflow-service.js
T
2026-05-20 09:38:07 +02:00

230 lines
6.1 KiB
JavaScript

/**
* Workflow Service
* Handles workflow fetching with lazy loading and caching
* Workflows are only loaded when first requested (not at startup)
*/
const apiService = require('./api-service').getInstance();
const profileManager = require('../config/profile-manager');
// Cache state
let workflowCache = null;
let cacheTimestamp = null;
const CACHE_TTL = parseInt(process.env.WORKFLOW_CACHE_TTL) || 3600000; // 1 hour in milliseconds
// Clear cache when profile changes — workflows are per-tenant, so the previous
// profile's cache is meaningless after a switch.
profileManager.onSwitch(() => clearCache());
/**
* Check if cache is still valid
*/
function isCacheValid() {
if (!workflowCache || !cacheTimestamp) {
return false;
}
const now = Date.now();
const age = now - cacheTimestamp;
return age < CACHE_TTL;
}
/**
* Fetch all workflows from API with pagination
* Uses high page size (5000) to minimize API calls
*/
async function fetchAllWorkflows() {
// Check cache validity
if (isCacheValid()) {
console.error('[Workflow] Using cached data');
return workflowCache;
}
console.error('[Workflow] Cache expired or empty, fetching from API...');
try {
let allWorkflows = [];
let offset = 0;
const pageSize = parseInt(process.env.WORKFLOW_PAGE_SIZE) || 5000;
const profile = profileManager.getCurrent();
const application = profile.application;
const tenant = profile.tenant;
while (true) {
const body = [application, tenant, pageSize, offset];
console.error(`[Workflow] Fetching page: offset=${offset}, pageSize=${pageSize}`);
// Use AD API (useAdApi=true)
const response = await apiService.post('/Workflow/GetByApplication', body, true);
// Extract entities array from response
const workflows = response?.entities || [];
// Check if response is valid
if (!workflows || workflows.length === 0) {
console.error('[Workflow] No more workflows to fetch');
break;
}
allWorkflows = allWorkflows.concat(workflows);
console.error(`[Workflow] Fetched ${workflows.length} workflows (total: ${allWorkflows.length})`);
// If we got less than page size, we've reached the last page
if (workflows.length < pageSize) {
console.error('[Workflow] Last page reached');
break;
}
offset += pageSize;
}
// Update cache
workflowCache = allWorkflows;
cacheTimestamp = Date.now();
console.error(`[Workflow] Successfully cached ${allWorkflows.length} workflows`);
return allWorkflows;
} catch (error) {
console.error('[Workflow] Error fetching workflows:', error.message);
throw new Error(`Failed to fetch workflows: ${error.message}`);
}
}
/**
* Search workflows by query string
* @param {string} query - Search query (matches name, description, etc.)
* @param {string|null} category - Optional category filter
* @param {number} limit - Maximum results to return
*/
async function searchWorkflows(query, category = null, limit = 50) {
const workflows = await fetchAllWorkflows();
let results = workflows;
// Filter by query if provided
if (query) {
const lowerQuery = query.toLowerCase();
results = results.filter(w => {
const name = (w.name || w.Name || '').toLowerCase();
const description = (w.description || w.Description || '').toLowerCase();
const code = (w.code || w.Code || '').toLowerCase();
return name.includes(lowerQuery) ||
description.includes(lowerQuery) ||
code.includes(lowerQuery);
});
}
// Filter by category if provided
if (category) {
const lowerCategory = category.toLowerCase();
results = results.filter(w => {
const wfCategory = (w.category || w.Category || '').toLowerCase();
return wfCategory.includes(lowerCategory);
});
}
// Limit results
return results.slice(0, limit);
}
/**
* Get workflow details by ID
* @param {string|number} workflowId - Workflow ID
*/
async function getWorkflowDetails(workflowId) {
const workflows = await fetchAllWorkflows();
// Try to find by Id, id, Code, code, Name, or name
const workflow = workflows.find(w =>
w.id === workflowId ||
w.Id === workflowId ||
w.id === parseInt(workflowId) ||
w.Id === parseInt(workflowId) ||
w.Code === workflowId ||
w.code === workflowId ||
w.Name === workflowId ||
w.name === workflowId
);
if (!workflow) {
throw new Error(`Workflow not found: ${workflowId}`);
}
return workflow;
}
/**
* List all workflow categories
*/
async function listWorkflowCategories() {
const workflows = await fetchAllWorkflows();
// Extract unique categories (try both lowercase and uppercase)
const categories = new Set();
workflows.forEach(w => {
const category = w.category || w.Category;
if (category) {
categories.add(category);
}
});
// Sort alphabetically
return Array.from(categories).sort();
}
/**
* Get workflow statistics
*/
async function getWorkflowStats() {
const workflows = await fetchAllWorkflows();
const categories = await listWorkflowCategories();
// Count workflows per category
const categoryCounts = {};
workflows.forEach(w => {
const cat = w.category || w.Category || 'Uncategorized';
categoryCounts[cat] = (categoryCounts[cat] || 0) + 1;
});
return {
total: workflows.length,
categories: categories.length,
categoryCounts,
cacheAge: cacheTimestamp ? Math.floor((Date.now() - cacheTimestamp) / 1000) : null
};
}
/**
* Clear workflow cache (force refresh on next request)
*/
function clearCache() {
workflowCache = null;
cacheTimestamp = null;
console.error('[Workflow] Cache cleared');
}
/**
* Get cache status
*/
function getCacheStatus() {
return {
cached: workflowCache !== null,
count: workflowCache ? workflowCache.length : 0,
timestamp: cacheTimestamp,
age: cacheTimestamp ? Math.floor((Date.now() - cacheTimestamp) / 1000) : null,
valid: isCacheValid()
};
}
module.exports = {
fetchAllWorkflows,
searchWorkflows,
getWorkflowDetails,
listWorkflowCategories,
getWorkflowStats,
clearCache,
getCacheStatus
};