feat: setup S2S integration, tailwind fixes, admin routing, nginx fallback

This commit is contained in:
2026-09-22 16:40:42 +05:30
parent 45d13fa7be
commit 05358be6c1
17 changed files with 522 additions and 149 deletions
+118 -38
View File
@@ -1,5 +1,7 @@
const axios = require('axios');
const FormData = require('form-data');
const Config = require('../models/Config');
const { decrypt } = require('../utils/crypto');
exports.analyzeImage = async (req, res) => {
try {
@@ -7,50 +9,128 @@ exports.analyzeImage = async (req, res) => {
return res.status(400).json({ error: 'No image provided' });
}
// In a real scenario, we would forward this to the Perfect Corp API
// For now, we will return a mock response matching the expected structure
// or you could proxy it like this:
/*
const formData = new FormData();
formData.append('image', req.file.buffer, req.file.originalname);
const response = await axios.post(process.env.PERFECT_CORP_API_URL, formData, {
headers: {
...formData.getHeaders(),
'Authorization': `Bearer ${process.env.PERFECT_CORP_API_KEY}`
}
});
return res.json({ result: response.data });
*/
// Fetch API Key from DB
const apiKeyConfig = await Config.findOne({ keyName: 'PERFECT_CORP_API_KEY' });
if (!apiKeyConfig) {
return res.status(500).json({ error: 'API Key configuration missing' });
}
const decryptedApiKey = decrypt(apiKeyConfig.encryptedValue, apiKeyConfig.iv);
// MOCK RESPONSE
console.log('Mocking perfect corp analysis for image:', req.file.originalname);
const mockResult = {
results: [
const baseUrl = process.env.PERFECT_CORP_API_URL || 'https://yce-api-01.makeupar.com/s2s/v2.0';
// Step 1: Initialize Upload
console.log('Step 1: Initializing Upload...');
const initPayload = {
files: [
{
data: [
{
url: 'https://example.com/mock-mask.zip'
}
]
content_type: req.file.mimetype || 'image/jpeg',
file_name: req.file.originalname || 'image.jpg',
file_size: req.file.size
}
],
// Add other mock data that the frontend expects to parse...
face_attributes: {
age: 30,
gender: 'female'
},
skin_issues: {
wrinkles: 10,
spots: 5,
acne: 2
}
]
};
res.json({ result: mockResult });
const initResponse = await axios.post(`${baseUrl}/file`, initPayload, {
headers: {
'Authorization': `Bearer ${decryptedApiKey}`,
'Content-Type': 'application/json'
}
});
const fileData = initResponse.data?.data?.files?.[0];
if (!fileData || !fileData.requests || !fileData.requests[0]) {
throw new Error('Failed to get upload URL from Perfect Corp API');
}
const fileId = fileData.file_id;
const uploadRequest = fileData.requests[0];
// Step 2: S3 Upload
console.log(`Step 2: Uploading image to S3 (fileId: ${fileId})...`);
await axios.put(uploadRequest.url, req.file.buffer, {
headers: uploadRequest.headers
});
// Step 3: Create Task
console.log('Step 3: Creating AI Task...');
const taskPayload = {
src_file_id: fileId,
dst_actions: [
"wrinkle", "pore", "texture", "acne", "moisture", "eye_bag",
"firmness", "age_spot", "radiance", "redness", "oiliness"
],
miniserver_args: {
enable_mask_overlay: false // Generate multiple output masks
},
format: "json"
};
const taskResponse = await axios.post(`${baseUrl}/task/skin-analysis`, taskPayload, {
headers: {
'Authorization': `Bearer ${decryptedApiKey}`,
'Content-Type': 'application/json'
}
});
const taskId = taskResponse.data?.data?.task_id;
if (!taskId) {
throw new Error('Failed to create AI task');
}
// Step 4: Poll Task Status
console.log(`Step 4: Polling task status for taskId: ${taskId}...`);
let taskFinished = false;
let finalResult = null;
let attempts = 0;
const maxAttempts = 30; // 60 seconds max
while (!taskFinished && attempts < maxAttempts) {
await new Promise(resolve => setTimeout(resolve, 2000)); // Wait 2 seconds
attempts++;
const pollResponse = await axios.get(`${baseUrl}/task/skin-analysis/${taskId}`, {
headers: {
'Authorization': `Bearer ${decryptedApiKey}`
}
});
const status = pollResponse.data?.data?.task_status;
if (status === 'success') {
taskFinished = true;
finalResult = pollResponse.data;
} else if (status === 'error' || status === 'failed') {
throw new Error(`AI Task failed with status: ${status}`);
} else {
console.log(`Polling... (status: ${status}, attempt: ${attempts})`);
}
}
if (!taskFinished) {
throw new Error('AI Task polling timed out');
}
console.log('Task finished successfully!');
// Format output to mirror the score_info.json structure
const outputArray = finalResult?.data?.results?.output || [];
const formattedScores = {};
for (const item of outputArray) {
if (item.type) {
formattedScores[item.type] = {
value: item.raw_score || item.ui_score || 0,
ui_score: item.ui_score,
raw_score: item.raw_score,
output_mask_name: (item.mask_urls && item.mask_urls.length > 0) ? item.mask_urls[0] : null
};
}
}
// Send formatted scores back to frontend
res.json({ result: { scores: formattedScores, raw_response: finalResult } });
} catch (error) {
console.error('Error analyzing image:', error);
console.error('Error analyzing image:', error.response ? JSON.stringify(error.response.data) : error.message);
res.status(500).json({ error: 'Failed to analyze image' });
}
};