direct-api-client.js•13 kB
#!/usr/bin/env node
/**
* Direct API Client v1.0.0
* Standalone API client for EuConquisto Composer (no browser automation required)
*
* Purpose: Enable direct API access without JWT redirect server or browser automation
* Status: EXPERIMENTAL - Alternative implementation approach
*
* @version 1.0.0 (January 15, 2025)
*/
import fetch from 'node-fetch';
import FormData from 'form-data';
import { DirectAPIConfig } from '../config/direct-api-config.js';
export class DirectAPIClient {
constructor(config = null) {
this.config = DirectAPIConfig;
this.apiLog = [];
// Load configuration if provided
if (config) {
this.config.loadFromConfig(config);
} else {
// Try to load from environment variables
this.config.loadFromEnvironment();
}
this.isReady = this.config.isValid();
}
/**
* Validate API configuration and connectivity
*/
async validateConnection() {
console.log('🔍 Validating direct API connection...');
if (!this.isReady) {
const status = this.config.getValidationStatus();
return {
success: false,
error: 'Configuration invalid',
details: status
};
}
try {
// Test user profile endpoint
const userResponse = await this.getUserProfile();
if (userResponse.success) {
console.log('✅ API connection validated successfully');
return {
success: true,
userProfile: userResponse.data,
config: this.config.getSummary()
};
} else {
console.log('❌ API connection validation failed');
return {
success: false,
error: 'User profile request failed',
details: userResponse
};
}
} catch (error) {
console.log('❌ API connection validation error:', error.message);
return {
success: false,
error: error.message,
networkError: true
};
}
}
/**
* Get user profile (test authentication)
*/
async getUserProfile() {
const url = this.config.buildURL(this.config.endpoints.userProfile);
const headers = this.config.getHeaders();
this.logAPI('GET_USER_PROFILE', 'Requesting user profile', url);
try {
const response = await fetch(url, {
method: 'GET',
headers: headers
});
const responseText = await response.text();
let parsedResponse = null;
try {
parsedResponse = JSON.parse(responseText);
} catch (e) {
// Response might not be JSON
}
const result = {
success: response.ok,
status: response.status,
statusText: response.statusText,
data: parsedResponse,
responseText: responseText
};
this.logAPI('GET_USER_PROFILE_RESULT', `Status: ${response.status}`, result.success);
return result;
} catch (error) {
const result = {
success: false,
error: error.message,
networkError: true
};
this.logAPI('GET_USER_PROFILE_ERROR', `Network error: ${error.message}`, false);
return result;
}
}
/**
* Get project information
*/
async getProjectInfo() {
const url = this.config.buildURL(this.config.endpoints.projectInfo, {
projectUid: this.config.authentication.projectUid
});
const headers = this.config.getHeaders();
this.logAPI('GET_PROJECT_INFO', 'Requesting project information', url);
try {
const response = await fetch(url, {
method: 'GET',
headers: headers
});
const responseText = await response.text();
let parsedResponse = null;
try {
parsedResponse = JSON.parse(responseText);
} catch (e) {
// Response might not be JSON
}
const result = {
success: response.ok,
status: response.status,
statusText: response.statusText,
data: parsedResponse,
responseText: responseText
};
this.logAPI('GET_PROJECT_INFO_RESULT', `Status: ${response.status}`, result.success);
return result;
} catch (error) {
const result = {
success: false,
error: error.message,
networkError: true
};
this.logAPI('GET_PROJECT_INFO_ERROR', `Network error: ${error.message}`, false);
return result;
}
}
/**
* Save composition directly via API
*/
async saveComposition(compositionData, title = 'Direct API Composition') {
console.log('💾 Saving composition via direct API...');
if (!this.isReady) {
return {
success: false,
error: 'API client not properly configured'
};
}
try {
// Select optimal connector
const connector = this.config.selectOptimalConnector();
if (!connector) {
return {
success: false,
error: 'No suitable connector available'
};
}
console.log(`📡 Using connector: ${connector.name || connector.uid}`);
// Build upload URL
const uploadUrl = this.config.buildURL(this.config.endpoints.uploadComposition, {
connectorUid: connector.uid,
projectUid: this.config.authentication.projectUid
});
// Prepare composition file
const timestamp = Date.now();
const safeName = title.replace(/[^a-zA-Z0-9]/g, '_');
const fileName = `composition_${timestamp}_${safeName}.rdpcomposer`;
const compositionJSON = JSON.stringify(compositionData, null, 2);
console.log(`📄 File: ${fileName} (${compositionJSON.length} bytes)`);
// Prepare FormData
const formData = new FormData();
formData.append('file', compositionJSON, {
filename: fileName,
contentType: 'application/json'
});
// Get upload headers (no Content-Type for FormData)
const headers = this.config.getUploadHeaders();
this.logAPI('SAVE_COMPOSITION', 'Uploading composition', {
url: uploadUrl,
fileName: fileName,
fileSize: compositionJSON.length,
connector: connector.name || connector.uid
});
// Execute upload
const response = await fetch(uploadUrl, {
method: 'POST',
headers: headers,
body: formData
});
const responseText = await response.text();
let parsedResponse = null;
try {
parsedResponse = JSON.parse(responseText);
} catch (e) {
// Response might not be JSON
}
const result = {
success: response.ok,
status: response.status,
statusText: response.statusText,
data: parsedResponse,
responseText: responseText,
uploadDetails: {
fileName: fileName,
fileSize: compositionJSON.length,
connector: connector.name || connector.uid,
projectUid: this.config.authentication.projectUid
}
};
if (result.success) {
// Try to extract composition UID
const compositionUid = this.extractCompositionUID(parsedResponse);
if (compositionUid) {
result.compositionUid = compositionUid;
console.log(`✅ Composition saved successfully: ${compositionUid}`);
} else {
console.log(`✅ Composition uploaded successfully (UID extraction failed)`);
}
} else {
console.log(`❌ Composition save failed: ${response.status} ${response.statusText}`);
}
this.logAPI('SAVE_COMPOSITION_RESULT', `Status: ${response.status}`, result.success);
return result;
} catch (error) {
console.log(`❌ Composition save error: ${error.message}`);
const result = {
success: false,
error: error.message,
networkError: true
};
this.logAPI('SAVE_COMPOSITION_ERROR', `Network error: ${error.message}`, false);
return result;
}
}
/**
* Extract composition UID from API response (same logic as current implementation)
*/
extractCompositionUID(responseData) {
if (!responseData) return null;
// Array response format
if (Array.isArray(responseData) && responseData[0]) {
const firstItem = responseData[0];
return firstItem.uid || firstItem.id || firstItem.compositionId || null;
}
// Direct object response
if (typeof responseData === 'object') {
return responseData.uid ||
responseData.id ||
responseData.compositionId ||
responseData.composition_uid ||
responseData.fileId ||
null;
}
// String response (direct UID)
if (typeof responseData === 'string' && responseData.length > 10) {
return responseData;
}
return null;
}
/**
* Create a complete composition workflow (generate + save)
*/
async createComposition(prompt, subject = 'General', gradeLevel = '6º ano') {
console.log('🎯 Creating composition via direct API workflow...');
console.log(` Prompt: ${prompt}`);
console.log(` Subject: ${subject}`);
console.log(` Grade Level: ${gradeLevel}\n`);
try {
// Step 1: Generate composition structure (simplified for testing)
const composition = this.generateTestComposition(prompt, subject, gradeLevel);
// Step 2: Save via direct API
const saveResult = await this.saveComposition(composition, prompt);
if (saveResult.success) {
return {
success: true,
composition: composition,
saveResult: saveResult,
compositionUid: saveResult.compositionUid,
message: 'Composition created and saved successfully via direct API'
};
} else {
return {
success: false,
error: 'Failed to save composition',
details: saveResult
};
}
} catch (error) {
return {
success: false,
error: error.message,
workflowError: true
};
}
}
/**
* Generate a test composition (simplified version for API testing)
*/
generateTestComposition(prompt, subject, gradeLevel) {
return {
metadata: {
title: prompt.substring(0, 50),
description: `Educational content about ${subject} for ${gradeLevel}`,
subject: subject,
gradeLevel: gradeLevel,
createdAt: new Date().toISOString(),
createdBy: 'Direct API Client v1.0.0'
},
structure: [
{
type: "head-1",
text: prompt.substring(0, 50),
level: 1
},
{
type: "text-1",
text: `<p>Este é um conteúdo educacional sobre <strong>${subject}</strong> criado via API direta.</p><p>Prompt: ${prompt}</p><p>Nível: ${gradeLevel}</p>`
},
{
type: "quiz-1",
questions: [
{
question: `Qual é o tema principal deste conteúdo sobre ${subject}?`,
options: [
"Conceitos básicos",
"Aplicações práticas",
"Teoria avançada",
"Exercícios"
],
correct: 0
}
]
}
]
};
}
/**
* Get API logs for debugging
*/
getAPILogs() {
return this.apiLog;
}
/**
* Log API operations
*/
logAPI(operation, details, success = true) {
this.apiLog.push({
timestamp: new Date().toISOString(),
operation: operation,
details: details,
success: success
});
}
/**
* Get configuration status
*/
getStatus() {
return {
ready: this.isReady,
config: this.config.getSummary(),
apiLogs: this.apiLog.length
};
}
}
// CLI testing utility
if (import.meta.url === `file://${process.argv[1]}`) {
console.log('🔗 Direct API Client v1.0.0');
console.log('============================\n');
const client = new DirectAPIClient();
console.log('📊 Client Status:');
console.log(JSON.stringify(client.getStatus(), null, 2));
if (client.isReady) {
console.log('\n🧪 Running API validation test...');
client.validateConnection().then(result => {
console.log('\n📋 Validation Result:');
console.log(JSON.stringify(result, null, 2));
if (result.success) {
console.log('\n✅ Direct API approach is functional!');
console.log('🎯 Next: Configure environment variables and test composition creation');
} else {
console.log('\n❌ Direct API requires configuration or has connectivity issues');
console.log('🎯 Next: Check authentication configuration and network connectivity');
}
}).catch(error => {
console.error('\n❌ Test execution error:', error.message);
});
} else {
console.log('\n⚠️ Client not ready - configuration required');
console.log('🎯 Configure authentication via environment variables or config object');
}
}
// Export already declared at the bottom