Initial commit

This commit is contained in:
Jordan
2026-04-01 23:16:45 +01:00
commit 91cfdaee72
200 changed files with 25589 additions and 0 deletions

View File

@@ -0,0 +1,587 @@
/**
* Acai CMS HTTP Client
*
* Centralizado helper para todas las llamadas HTTP a Acai CMS.
* Proporciona métodos consistentes para interactuar con:
* - Admin panel (admin.php)
* - Viewer functions API
* - SaaS API
* - File upload endpoints
*
* Ventajas:
* - Consistencia en headers, manejo de errores, logging
* - Reduce duplicación de código
* - Facilita mantenimiento y debugging
* - Centraliza URLs y configuración
*/
import axios from 'axios';
import { getSessionCredentials } from '../../auth/index.js';
import { CMS_URL } from '../../config/index.js';
import { assertSafeCmsTarget } from '../../utils/cmsTargetSafety.js';
/**
* AcaiHttpClient - Helper para solicitudes HTTP a Acai CMS
*/
export class AcaiHttpClient {
static resolveCmsTarget(target) {
const { publicUrl, apiUrl, forgeHost } = assertSafeCmsTarget(target, "AcaiHttpClient");
const headers = {};
if (forgeHost) {
headers.Host = forgeHost;
}
return {
publicUrl,
apiUrl,
headers,
};
}
static buildViewerUrl(target, query = "") {
const { apiUrl } = AcaiHttpClient.resolveCmsTarget(target);
const baseUrl = apiUrl.endsWith("/") ? apiUrl.slice(0, -1) : apiUrl;
return `${baseUrl}/cms/lib/viewer_functions.php${query ? `?${query}` : ""}`;
}
static buildViewerHeaders(target, extraHeaders = {}) {
const { headers } = AcaiHttpClient.resolveCmsTarget(target);
return {
"Content-Type": "application/json",
...headers,
...extraHeaders,
};
}
static async postViewerAction(target, actionWs, payload, token, tokenHash, extraHeaders = {}, timeout = 30000) {
const viewerUrl = AcaiHttpClient.buildViewerUrl(target, `action_ws=${actionWs}`);
const body = {
...payload,
token,
tokenHash,
};
return axios.post(viewerUrl, body, {
headers: AcaiHttpClient.buildViewerHeaders(target, extraHeaders),
timeout,
});
}
/**
* POST a admin.php con URLSearchParams
* @param {string} website - Website/domain (no usado, se usa CMS_URL del config)
* @param {URLSearchParams} params - Parámetros del formulario
* @param {string} token - Token Acai
* @returns {Promise<Object>} Respuesta del servidor
*/
static async postAdminForm(website, params, token) {
const cmsUrl = `${CMS_URL}/admin.php`;
try {
console.error(`[AcaiHttpClient] postAdminForm - START: ${cmsUrl}`);
const response = await axios.post(cmsUrl, params, {
headers: {
"X-Acai-Token": token,
"Content-Type": "application/x-www-form-urlencoded"
},
timeout: 30000
});
console.error(`[AcaiHttpClient] postAdminForm - SUCCESS: ${cmsUrl} (${response.status})`);
return response;
} catch (error) {
console.error(`[AcaiHttpClient] postAdminForm - ERROR: ${cmsUrl} - ${error.message}`);
if (error.response) {
console.error(`[AcaiHttpClient] Response status: ${error.response.status}`);
console.error(`[AcaiHttpClient] Response data:`, error.response.data?.substring ? error.response.data.substring(0, 200) : error.response.data);
} else if (error.code) {
console.error(`[AcaiHttpClient] Error code: ${error.code}`);
}
throw error;
}
}
/**
* POST a admin.php con FormData (para uploads)
* @param {string} website - Website/domain (no usado, se usa CMS_URL del config)
* @param {FormData} formData - Datos del formulario
* @param {string} token - Token Acai
* @returns {Promise<Object>} Respuesta del servidor
*/
static async postAdminFormData(website, formData, token) {
const cmsUrl = `${CMS_URL}/admin.php`;
try {
console.error(`[AcaiHttpClient] postAdminFormData - START: ${cmsUrl}`);
const response = await axios.post(cmsUrl, formData, {
headers: {
...formData.getHeaders(),
"X-Acai-Token": token
},
timeout: 30000
});
console.error(`[AcaiHttpClient] postAdminFormData - SUCCESS: ${cmsUrl} (${response.status})`);
return response;
} catch (error) {
console.error(`[AcaiHttpClient] postAdminFormData - ERROR: ${cmsUrl} - ${error.message}`);
if (error.response) {
console.error(`[AcaiHttpClient] Response status: ${error.response.status}`);
console.error(`[AcaiHttpClient] Response data:`, error.response.data?.substring ? error.response.data.substring(0, 200) : error.response.data);
} else if (error.code) {
console.error(`[AcaiHttpClient] Error code: ${error.code}`);
}
throw error;
}
}
/**
* GET a admin.php con query parameters
* @param {string} website - Website/domain (no usado, se usa CMS_URL del config)
* @param {URLSearchParams | string} params - Parámetros de query
* @param {string} token - Token Acai
* @returns {Promise<Object>} Respuesta del servidor
*/
static async getAdminQuery(website, params, token) {
const cmsUrl = `${CMS_URL}/admin.php`;
const queryString = params instanceof URLSearchParams
? params.toString()
: params;
try {
console.error(`[AcaiHttpClient] getAdminQuery - START: ${cmsUrl}?${queryString.substring(0, 100)}`);
const response = await axios.get(
`${cmsUrl}?${queryString}`,
{
headers: {
"X-Acai-Token": token,
"X-Requested-With": "XMLHttpRequest"
},
timeout: 30000
}
);
console.error(`[AcaiHttpClient] getAdminQuery - SUCCESS: ${cmsUrl} (${response.status})`);
return response;
} catch (error) {
console.error(`[AcaiHttpClient] getAdminQuery - ERROR: ${cmsUrl} - ${error.message}`);
if (error.response) {
console.error(`[AcaiHttpClient] Response status: ${error.response.status}`);
console.error(`[AcaiHttpClient] Response data:`, error.response.data?.substring ? error.response.data.substring(0, 200) : error.response.data);
} else if (error.code) {
console.error(`[AcaiHttpClient] Error code: ${error.code}`);
}
throw error;
}
}
/**
* POST a viewer_functions.php vía getApiClient
* Requiere llamar desde dentro de withAuth para tener acceso a getApiClient
* @param {Object} client - cliente de axios (getApiClient)
* @param {Object} payload - Payload con action_ws y otros parámetros
* @returns {Promise<Object>} Respuesta del servidor
*/
static async postViewerFunctions(client, payload) {
return client.post("/cms/lib/viewer_functions.php", payload);
}
/**
* POST a viewer_functions.php para saveLexicalData (secciones, contenido)
* @param {string} web_url - URL base del sitio (ej: http://localhost:PORT)
* @param {Object} credentials - {token, tokenHash}
* @param {Object} data - Datos a guardar
* @returns {Promise<Object>} Respuesta del servidor
*/
static async saveLexicalData(target, credentials, data) {
const viewerUrl = AcaiHttpClient.buildViewerUrl(target);
const payload = {
action_ws: 'saveLexicalData',
token: credentials.token,
tokenHash: credentials.tokenHash,
rawDataSended: true,
...data
};
return axios.post(viewerUrl, payload, {
headers: AcaiHttpClient.buildViewerHeaders(target)
});
}
/**
* POST para generar módulo desde HTML
* @param {Object} moduleData - Datos del módulo
* @param {string} token - Token Acai
* @returns {Promise<Object>} Respuesta del servidor
*/
static async generateModuleFromString(moduleData, token) {
const cmsUrl = 'https://acai.cms.cocosolution.com/admin.php?menu=apartados&action=edit&generateModuleFromString=1';
return axios.post(cmsUrl, moduleData, {
headers: {
"Content-Type": "application/json",
"X-Acai-Token": token
}
});
}
/**
* POST a viewer_functions para CMS API (insert, update, delete, get)
* @param {string} web_url - URL base del sitio (ej: http://localhost:PORT)
* @param {string} action - 'insert', 'update', 'delete', 'get'
* @param {Object} payload - Datos de la operación
* @param {string} token - Token Acai
* @returns {Promise<Object>} Respuesta del servidor
*/
static async postCmsApi(target, action, payload, token, tokenHash) {
const viewerUrl = AcaiHttpClient.buildViewerUrl(target, `action_ws=cmsApi&subaction=${action}`);
try {
console.error(`[AcaiHttpClient] postCmsApi - START: ${action} on ${viewerUrl}`);
console.error(`[AcaiHttpClient] Payload:`, JSON.stringify(payload).substring(0, 500));
console.error(`[AcaiHttpClient] Token: ${token ? '****' + token.slice(-4) : 'No token provided'}`);
payload["token"] = token;
payload["tokenHash"] = tokenHash;
const response = await axios.post(viewerUrl, payload, {
headers: AcaiHttpClient.buildViewerHeaders(target, {
"X-Acai-Token": token
}),
timeout: 30000
});
console.error(`[AcaiHttpClient] postCmsApi - SUCCESS: ${action} (${response.status})`);
return response;
} catch (error) {
console.error(`[AcaiHttpClient] postCmsApi - ERROR: ${action} on ${viewerUrl} - ${error.message}`);
if (error.response) {
console.error(`[AcaiHttpClient] Response status: ${error.response.status}`);
console.error(`[AcaiHttpClient] Response data:`, error.response.data?.substring ? error.response.data.substring(0, 200) : error.response.data);
} else if (error.code) {
console.error(`[AcaiHttpClient] Error code: ${error.code}`);
}
throw error;
}
}
/**
* POST a viewer_functions para checkModuleCode
* @param {string} web_url - URL base del sitio (ej: http://localhost:PORT)
* @param {string} token - Token Acai
* @param {Object} data - {moduleName, vars}
* @returns {Promise<Object>} Respuesta del servidor
*/
static async checkModuleCode(target, token, data) {
const viewerUrl = AcaiHttpClient.buildViewerUrl(target, "action_ws=checkModuleCode");
try {
data["token"] = token;
console.error(`[AcaiHttpClient] checkModuleCode - START: ${viewerUrl}`);
const response = await axios.post(viewerUrl, data, {
headers: AcaiHttpClient.buildViewerHeaders(target),
timeout: 30000
});
console.error(`[AcaiHttpClient] checkModuleCode - SUCCESS: ${viewerUrl} (${response.status})`);
return response;
} catch (error) {
console.error(`[AcaiHttpClient] checkModuleCode - ERROR: ${viewerUrl}`, error.message);
throw error;
}
}
/**
* POST a viewer_functions para addModuleToRecord
* @param {string} web_url - URL base del sitio (ej: http://localhost:PORT)
* @param {string} token - Token Acai
* @param {Object} data - {moduleName, vars}
* @returns {Promise<Object>} Respuesta del servidor
*/
static async addModuleToRecord(target, token, tokenHash, data) {
const viewerUrl = AcaiHttpClient.buildViewerUrl(target, "action_ws=addModuleToRecord");
try {
data["token"] = token;
data["tokenHash"] = tokenHash;
console.error(`[AcaiHttpClient] addModuleToRecord - START: ${viewerUrl}`);
const response = await axios.post(viewerUrl, data, {
headers: AcaiHttpClient.buildViewerHeaders(target),
timeout: 30000
});
console.error(`[AcaiHttpClient] addModuleToRecord - SUCCESS: ${viewerUrl} (${response.status})`);
return response;
} catch (error) {
console.error(`[AcaiHttpClient] addModuleToRecord - ERROR: ${viewerUrl}`, error.message);
throw error;
}
}
/**
* POST a mcp_respond.php para setModuleConfigVars
* @param {string} web_url - URL base del sitio (ej: http://localhost:PORT)
* @param {string} token - Token Acai
* @param {string} tokenHash - Token hash Acai
* @param {Object} data - {tableName, recordNum, sectionId, vars}
* @returns {Promise<Object>} Respuesta del servidor
*/
static async setModuleConfigVars(target, token, tokenHash, data) {
const url = AcaiHttpClient.buildViewerUrl(target, "action_ws=setModuleConfigVars");
try {
data["token"] = token;
data["tokenHash"] = tokenHash;
console.error(`[AcaiHttpClient] setModuleConfigVars - START: ${url}`);
const response = await axios.post(url, data, {
headers: AcaiHttpClient.buildViewerHeaders(target),
timeout: 30000
});
console.error(`[AcaiHttpClient] setModuleConfigVars - SUCCESS: ${url} (${response.status})`);
return response;
} catch (error) {
console.error(`[AcaiHttpClient] setModuleConfigVars - ERROR: ${url}`, error.message);
throw error;
}
}
/**
* POST a viewer_functions para getModuleConfigVars
* @param {string} web_url - URL base del sitio (ej: http://localhost:PORT)
* @param {string} token - Token Acai
* @param {string} tokenHash - Token hash Acai
* @param {Object} data - {tableName, recordNum, sectionId}
* @returns {Promise<Object>} Respuesta del servidor
*/
static async getModuleConfigVars(target, token, tokenHash, data) {
const url = AcaiHttpClient.buildViewerUrl(target, "action_ws=getModuleConfigVars");
try {
data["token"] = token;
data["tokenHash"] = tokenHash;
console.error(`[AcaiHttpClient] getModuleConfigVars - START: ${url}`);
const response = await axios.post(url, data, {
headers: AcaiHttpClient.buildViewerHeaders(target),
timeout: 30000
});
console.error(`[AcaiHttpClient] getModuleConfigVars - SUCCESS: ${url} (${response.status})`);
return response;
} catch (error) {
console.error(`[AcaiHttpClient] getModuleConfigVars - ERROR: ${url}`, error.message);
throw error;
}
}
/**
* POST para subir imagen a campo de registro
* @param {string} website - Website/domain (no usado, se usa CMS_URL del config)
* @param {string} tableName - Nombre de la tabla
* @param {string} recordId - ID del registro
* @param {string} fieldName - Nombre del campo
* @param {FormData} formData - Datos del archivo
* @param {string} token - Token Acai
* @returns {Promise<Object>} Respuesta del servidor
*/
static async uploadRecordImage(website, tableName, recordId, fieldName, formData, token) {
const uploadUrl = `${CMS_URL}/lib/menus/modals/plupload/multiupload/upload.php?menu=${tableName}&fieldName=${fieldName}&num=${recordId}&preSaveTempId=`;
return axios.post(uploadUrl, formData, {
headers: {
...formData.getHeaders(),
"X-Acai-Token": token
}
});
}
/**
* POST a SaaS API para guardar esquema
* @param {Object} payload - {action, type, schema, dir, id, ...}
* @param {string} token - Token autenticación
* @returns {Promise<Object>} Respuesta del servidor
*/
static async saasPostRequest(payload, token) {
const SAAS_URL = 'https://ws.cocosolution.com/api/schemas/';
try {
console.error(`[AcaiHttpClient] saasPostRequest - START: ${SAAS_URL} (action: ${payload.action})`);
const response = await axios.post(SAAS_URL, payload, {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
timeout: 30000
});
console.error(`[AcaiHttpClient] saasPostRequest - SUCCESS: ${SAAS_URL} (${response.status})`);
return response;
} catch (error) {
console.error(`[AcaiHttpClient] saasPostRequest - ERROR: ${SAAS_URL} - ${error.message}`);
if (error.response) {
console.error(`[AcaiHttpClient] Response status: ${error.response.status}`);
console.error(`[AcaiHttpClient] Response data:`, error.response.data?.substring ? error.response.data.substring(0, 200) : error.response.data);
} else if (error.code) {
console.error(`[AcaiHttpClient] Error code: ${error.code}`);
}
throw error;
}
}
/**
* PUT a SaaS API para actualizar esquema
* @param {Object} payload - {action, type, schema, dir, id}
* @param {string} token - Token autenticación
* @returns {Promise<Object>} Respuesta del servidor
*/
static async saasPutRequest(payload, token) {
const SAAS_URL = 'https://ws.cocosolution.com/api/schemas/';
try {
console.error(`[AcaiHttpClient] saasPutRequest - START: ${SAAS_URL} (action: ${payload.action})`);
const response = await axios.put(SAAS_URL, payload, {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
timeout: 30000
});
console.error(`[AcaiHttpClient] saasPutRequest - SUCCESS: ${SAAS_URL} (${response.status})`);
return response;
} catch (error) {
console.error(`[AcaiHttpClient] saasPutRequest - ERROR: ${SAAS_URL} - ${error.message}`);
if (error.response) {
console.error(`[AcaiHttpClient] Response status: ${error.response.status}`);
console.error(`[AcaiHttpClient] Response data:`, error.response.data?.substring ? error.response.data.substring(0, 200) : error.response.data);
} else if (error.code) {
console.error(`[AcaiHttpClient] Error code: ${error.code}`);
}
throw error;
}
}
}
/**
* Helper para construir parámetros comunes de formulario
*/
export class FormParamsBuilder {
static buildRecordSaveParams(tableName, recordId, fields, enlace) {
const params = new URLSearchParams();
params.append('menu', tableName);
params.append('_defaultAction', 'save');
params.append('num', recordId ? String(recordId) : '');
params.append('type', '');
params.append('preSaveTempId', Date.now().toString());
params.append('action=save', 'Guardar');
// Agregar todos los campos
for (const [fieldName, value] of Object.entries(fields)) {
if (fieldName === 'enlace') continue;
if (value !== null && value !== undefined) {
const strValue = String(value);
params.append(fieldName, strValue);
// Detectar y descomponer fechas
const dateRegex = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/;
const match = strValue.match(dateRegex);
if (match) {
params.append(`${fieldName}:year`, match[1]);
params.append(`${fieldName}:mon`, match[2]);
params.append(`${fieldName}:day`, match[3]);
params.append(`${fieldName}:hour24`, match[4]);
params.append(`${fieldName}:min`, match[5]);
}
}
}
params.append('enlace', enlace);
return params;
}
static buildDeleteRecordsParams(tableName, recordIds) {
const params = new URLSearchParams();
params.append('menu', tableName);
params.append('_defaultAction', 'list');
params.append('page', '1');
params.append('_advancedAction', 'eraseRecords');
params.append('_advancedActionSubmit', 'Ejecutar');
recordIds.forEach(id => {
params.append('selectedRecords[]', String(id));
});
return params;
}
static buildTableCreateParams(menuName, tableName, type, enlace, seo_metas, menuOrder) {
return new URLSearchParams({
menu: "database",
_defaultAction: "addTable_save",
type: type,
preset: "",
enlace: enlace ? "on" : "",
seo_metas: seo_metas ? "on" : "",
menuName: menuName,
menuOrder: menuOrder.toString(),
tableName: tableName
});
}
static buildTableDeleteParams(tableName) {
const params = new URLSearchParams();
params.append('menu', 'database');
params.append('action', 'editTable');
params.append('dropTable', '1');
params.append('tableName', tableName);
return params;
}
static buildFieldEditParams(tableName, multipleFields) {
const params = new URLSearchParams();
params.append('menu', 'database');
params.append('_defaultAction', 'editTable');
params.append('editField', '1');
params.append('tableName', tableName);
params.append('save', '1');
params.append('multipleFields', JSON.stringify(multipleFields));
return params;
}
static buildFieldDeleteParams(tableName, fieldname) {
const params = new URLSearchParams();
params.append('menu', 'database');
params.append('action', 'editTable');
params.append('editField', '1');
params.append('tableName', tableName);
params.append('fieldname', fieldname);
params.append('deleteField', '1');
return params;
}
}
/**
* Helper para construir URLs de query
*/
export class QueryParamsBuilder {
static buildListRecordsQuery(tableName, page, keyword) {
const params = new URLSearchParams({
menu: tableName,
json: "1"
});
if (page) params.append("page", String(page));
if (keyword) params.append("keyword", keyword);
return params;
}
static buildListUploadsQuery(tableName, recordId, fieldName) {
return new URLSearchParams({
menu: tableName,
action: 'uploadList',
fieldName: fieldName,
num: recordId,
preSaveTempId: '',
json: '1'
});
}
static buildDeleteUploadQuery(tableName, recordId, fieldName, uploadId) {
return new URLSearchParams({
menu: tableName,
action: 'uploadErase',
fieldName: fieldName,
uploadNum: uploadId,
num: recordId,
preSaveTempId: ''
});
}
}
export default AcaiHttpClient;