import { IExecuteFunctions, INodeType, INodeTypeDescription, INodeExecutionData, NodeOperationError, IHttpRequestMethods, IDataObject, } from 'n8n-workflow'; /** * Mevspace node — imperative style with explicit execute(). * * Covers three resources that all share the same base URL and access-token * query-string authentication: * 1. Dedicated — list / get / order KVM / reinstall / restart / start / stop * 2. Startup Script — list * 3. Template — list (with type filter) * * Uses explicit execute() method for better error handling and control. */ export class Mevspace implements INodeType { description: INodeTypeDescription = { displayName: 'Mevspace', name: 'mevspace', icon: 'file:mevspace.svg', group: ['transform'], version: 1, description: 'Manage Mevspace dedicated servers, startup scripts, and OS templates', defaults: { name: 'Mevspace' }, inputs: ['main'], outputs: ['main'], usableAsTool: true, credentials: [ { name: 'mevspaceApi', required: true }, ], properties: [ // ───────────────────────────────────────────────────────────── // Resource selector // ───────────────────────────────────────────────────────────── { displayName: 'Resource', name: 'resource', type: 'options', noDataExpression: true, options: [ { name: 'Dedicated Server', value: 'dedicated' }, { name: 'Startup Script', value: 'startupScript' }, { name: 'Template', value: 'template' }, ], default: 'dedicated', }, // ───────────────────────────────────────────────────────────── // Operation selector — options shown per resource // ───────────────────────────────────────────────────────────── { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, displayOptions: { show: { resource: ['dedicated'] } }, options: [ { name: 'Get All', value: 'getAll', action: 'List all dedicated servers' }, { name: 'Get Details', value: 'get', action: 'Get a dedicated server' }, { name: 'Order KVM', value: 'orderKvm', action: 'Order KVM for a dedicated server' }, { name: 'Reinstall', value: 'reinstall', action: 'Reinstall a dedicated server' }, { name: 'Restart', value: 'restart', action: 'Restart a dedicated server' }, { name: 'Start', value: 'start', action: 'Start a dedicated server' }, { name: 'Stop', value: 'stop', action: 'Stop a dedicated server' }, ], default: 'getAll', }, { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, displayOptions: { show: { resource: ['startupScript'] } }, options: [ { name: 'Get All', value: 'getAll', action: 'List all startup scripts' }, ], default: 'getAll', }, { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, displayOptions: { show: { resource: ['template'] } }, options: [ { name: 'Get All', value: 'getAll', action: 'List all OS templates' }, { name: 'Get By ID', value: 'getById', action: 'Find an OS template by ID' }, ], default: 'getAll', }, // ───────────────────────────────────────────────────────────── // Dedicated — shared hostname field (shown for all dedicated ops) // ───────────────────────────────────────────────────────────── { displayName: 'Hostname', name: 'hostname', type: 'string', required: true, default: '', placeholder: 'n490afb.mevnode.com', description: 'Dedicated server hostname', displayOptions: { show: { resource: ['dedicated'], operation: ['get', 'orderKvm', 'reinstall', 'restart', 'start', 'stop'], }, }, }, // ───────────────────────────────────────────────────────────── // Dedicated — Order KVM options // ───────────────────────────────────────────────────────────── { displayName: 'Client IP', name: 'clientIp', type: 'string', default: '', description: 'Client IP to assign to the KVM', displayOptions: { show: { resource: ['dedicated'], operation: ['orderKvm'] }, }, }, { displayName: 'Whitelabel', name: 'whitelabel', type: 'boolean', default: false, description: 'Whether to create a whitelabel KVM', displayOptions: { show: { resource: ['dedicated'], operation: ['orderKvm'] }, }, }, // ───────────────────────────────────────────────────────────── // Dedicated — Reinstall options // ───────────────────────────────────────────────────────────── { displayName: 'Template ID', name: 'templateId', type: 'number', default: 0, description: 'OS template id to install (use the Template resource to list available ids)', displayOptions: { show: { resource: ['dedicated'], operation: ['reinstall'] }, }, }, { displayName: 'Raid Value', name: 'raidValue', type: 'options', options: [ { name: 'RAID 0', value: 'raid0' }, { name: 'RAID 1', value: 'raid1' }, { name: 'RAID 5', value: 'raid5' }, { name: 'RAID 10', value: 'raid10' }, ], default: 'raid1', description: 'Raid type to use during installation', displayOptions: { show: { resource: ['dedicated'], operation: ['reinstall'] }, }, }, { displayName: 'Startup Script ID', name: 'startupScriptId', type: 'number', default: 0, description: 'Startup script id to run after install (use the Startup Script resource to list available ids)', displayOptions: { show: { resource: ['dedicated'], operation: ['reinstall'] }, }, }, // ───────────────────────────────────────────────────────────── // Template — type filter // ───────────────────────────────────────────────────────────── { displayName: 'Template Type', name: 'templateType', type: 'options', options: [ { name: 'Dedicated', value: 'dedicated' }, { name: 'VPS', value: 'vps' }, { name: 'VPS Cloud', value: 'vps_cloud' }, ], default: 'dedicated', description: 'Filter templates by server type', displayOptions: { show: { resource: ['template'], operation: ['getAll', 'getById'] }, }, }, // ───────────────────────────────────────────────────────────── // Template — Get By ID // ───────────────────────────────────────────────────────────── { displayName: 'Template ID', name: 'searchTemplateId', type: 'number', required: true, default: 0, description: 'The ID of the template to look for in the list', displayOptions: { show: { resource: ['template'], operation: ['getById'] }, }, }, ], }; async execute(this: IExecuteFunctions): Promise { const items = this.getInputData(); const returnData: INodeExecutionData[] = []; const credentials = await this.getCredentials('mevspaceApi'); const accessToken = credentials.accessToken as string; const baseURL = 'https://api.mevspace.com'; const resource = this.getNodeParameter('resource', 0) as string; const operation = this.getNodeParameter('operation', 0) as string; for (let i = 0; i < items.length; i++) { try { let endpoint = ''; let method: IHttpRequestMethods = 'GET'; let body: IDataObject | undefined; let qs: IDataObject = { 'access-token': accessToken }; if (resource === 'dedicated') { if (operation === 'getAll') { endpoint = '/dedicateds'; method = 'GET'; } else if (operation === 'get') { const hostname = this.getNodeParameter('hostname', i) as string; endpoint = `/dedicateds/${hostname}`; method = 'GET'; } else if (operation === 'orderKvm') { const hostname = this.getNodeParameter('hostname', i) as string; const clientIp = this.getNodeParameter('clientIp', i) as string; const whitelabel = this.getNodeParameter('whitelabel', i) as boolean; endpoint = `/dedicateds/${hostname}/order-kvm`; method = 'POST'; body = { client_ip: clientIp, whitelabel, }; } else if (operation === 'reinstall') { const hostname = this.getNodeParameter('hostname', i) as string; const templateId = this.getNodeParameter('templateId', i) as number; const raidValue = this.getNodeParameter('raidValue', i) as string; const startupScriptId = this.getNodeParameter('startupScriptId', i) as number; endpoint = `/dedicateds/${hostname}/reinstall`; method = 'POST'; body = { template_id: templateId, raid_value: raidValue, startup_script_id: startupScriptId, }; } else if (operation === 'restart') { const hostname = this.getNodeParameter('hostname', i) as string; endpoint = `/dedicateds/${hostname}/restart`; method = 'POST'; } else if (operation === 'start') { const hostname = this.getNodeParameter('hostname', i) as string; endpoint = `/dedicateds/${hostname}/start`; method = 'POST'; } else if (operation === 'stop') { const hostname = this.getNodeParameter('hostname', i) as string; endpoint = `/dedicateds/${hostname}/stop`; method = 'POST'; } } else if (resource === 'startupScript') { if (operation === 'getAll') { endpoint = '/startup-scripts'; method = 'GET'; } } else if (resource === 'template') { const templateType = this.getNodeParameter('templateType', i) as string; endpoint = '/templates'; method = 'GET'; qs = { ...qs, type: templateType, }; } if (!endpoint) { throw new NodeOperationError( this.getNode(), `Unknown operation: ${resource}.${operation}`, { itemIndex: i } ); } const options = { method, uri: `${baseURL}${endpoint}`, qs, headers: { Accept: 'application/json', 'Content-Type': 'application/json', }, json: true, resolveWithFullResponse: false, simple: true, }; if (body && method !== 'GET') { Object.assign(options, { body }); } const responseData = await this.helpers.request(options); // ── Template: Get By ID — filter locally after fetching all ── if (resource === 'template' && operation === 'getById') { const searchId = this.getNodeParameter('searchTemplateId', i) as number; const list = Array.isArray(responseData) ? responseData : []; const found = list.find((t: IDataObject) => Number(t.id) === searchId); if (found) { returnData.push({ json: { found: true, template: found } }); } else { returnData.push({ json: { found: false, message: `Template with ID ${searchId} not found in the list.`, }, }); } } else if (Array.isArray(responseData)) { for (const item of responseData) { returnData.push({ json: item }); } } else if (responseData !== null && responseData !== undefined) { returnData.push({ json: responseData }); } else { // Handle empty response returnData.push({ json: { success: true } }); } } catch (error) { // Properly handle and format errors const errorMessage = error instanceof Error ? error.message : String(error); throw new NodeOperationError( this.getNode(), `Mevspace API error: ${errorMessage}`, { itemIndex: i, description: errorMessage, } ); } } return [returnData]; } }