diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 793498e..a8caaa4 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -13,6 +13,7 @@ "cors": "^2.8.6", "dotenv": "^16.6.1", "express": "^4.22.1", + "express-basic-auth": "^1.2.1", "html2canvas": "^1.4.1", "multer": "^1.4.5-lts.1" } @@ -77,6 +78,24 @@ "node": ">= 0.6.0" } }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, "node_modules/body-parser": { "version": "1.20.4", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", @@ -441,6 +460,15 @@ "url": "https://opencollective.com/express" } }, + "node_modules/express-basic-auth": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/express-basic-auth/-/express-basic-auth-1.2.1.tgz", + "integrity": "sha512-L6YQ1wQ/mNjVLAmK3AG1RK6VkokA1BIY6wmiH304Xtt/cLTps40EusZsU1Uop+v9lTDPxdtzbFmdXfFO3KEnwA==", + "license": "MIT", + "dependencies": { + "basic-auth": "^2.0.1" + } + }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", diff --git a/frontend/package.json b/frontend/package.json index 4bb6b41..3225ea5 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,6 +13,7 @@ "cors": "^2.8.6", "dotenv": "^16.6.1", "express": "^4.22.1", + "express-basic-auth": "^1.2.1", "html2canvas": "^1.4.1", "multer": "^1.4.5-lts.1" } diff --git a/frontend/public/js/media.js b/frontend/public/js/media.js index fa82feb..9b71b55 100644 --- a/frontend/public/js/media.js +++ b/frontend/public/js/media.js @@ -393,8 +393,8 @@ const mediaLibrary = { try { // 1. Busca todo o conteúdo relevante (posts, páginas, rascunhos) const [posts, pages, drafts] = await Promise.all([ - wpAPI.fetchContent('posts', true), - wpAPI.fetchContent('pages', true), + wpAPI.request('/wp/posts?_fields=id,title,status,type,content,featured_media&per_page=10&status=publish,draft'), + wpAPI.request('/wp/pages?_fields=id,title,status,type,content,featured_media&per_page=10&status=publish,draft'), fetch('/api/drafts').then(r => r.json()) ]); diff --git a/frontend/server.js b/frontend/server.js index 602dc3a..254c2f4 100644 --- a/frontend/server.js +++ b/frontend/server.js @@ -3,6 +3,8 @@ const multer = require('multer'); const cors = require('cors'); const axios = require('axios'); const path = require('path'); +const basicAuth = require('express-basic-auth'); + require('dotenv').config({ path: '../.env' }); const { GoogleGenerativeAI } = require('@google/generative-ai'); @@ -20,6 +22,20 @@ app.use(express.static(path.join(__dirname, 'public'))); const storage = multer.memoryStorage(); const upload = multer({ storage: storage }); +if (!process.env.DASHBOARD_USER || !process.env.DASHBOARD_PASSWORD) { + console.error("❌ CRITICAL: Missing DASHBOARD_USER and DASHBOARD_PASSWORD in .env"); + process.exit(1); +} + +const authMiddleware = basicAuth({ + users: { [process.env.DASHBOARD_USER]: process.env.DASHBOARD_PASSWORD }, + challenge: true, + realm: 'NeuroEngine Mission Control' +}); + +app.use('/api', authMiddleware); + + // Initialize Gemini SDK const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY); const VISION_MODEL = 'gemini-2.5-flash'; @@ -141,7 +157,13 @@ app.get('/api/wp-settings', async (req, res) => { headers: { 'Authorization': `Basic ${WP_AUTH}` } }); res.json(response.data); - } catch (e) { res.status(500).json({ error: e.message }); } + } catch (e) { + console.error("❌ [CHAT ERROR]", e.message); + res.status(500).json({ + error: e.message, + reply: "Não foi possível processar a requisição de chat no momento." + }); + } }); app.post('/api/wp-settings', async (req, res) => { @@ -150,7 +172,13 @@ app.post('/api/wp-settings', async (req, res) => { headers: { 'Authorization': `Basic ${WP_AUTH}`, 'Content-Type': 'application/json' } }); res.json(response.data); - } catch (e) { res.status(500).json({ error: e.message }); } + } catch (e) { + console.error("❌ [BLUEPRINT ERROR]", e.message); + res.status(500).json({ + error: e.message, + html: "
Erro na geração do Blueprint.
" + }); + } }); // Endpoint especial para Upload de Mídia (Multipart/Form-Data) app.post('/api/wp-upload-media', upload.shared ? upload.single('file') : upload.single('file'), async (req, res) => { @@ -195,7 +223,11 @@ app.post('/api/ai/generate', async (req, res) => { res.json({ text: resp.text() }); } catch (e) { console.error("❌ [AI PROXY ERROR]", e.message); - res.status(500).json({ error: e.message }); + // Fallback response for missing API key or timeout + res.status(500).json({ + error: "Serviço de IA indisponível. " + e.message, + text: "Não foi possível gerar uma resposta no momento. Verifique as configurações de API ou tente novamente mais tarde." + }); } }); @@ -320,7 +352,10 @@ app.post('/api/agents/generate-pipeline', async (req, res) => { res.json({ success: true, draft: newDraft }); } catch (e) { console.error("❌ [PIPELINE ERROR]", e.message); - res.status(500).json({ error: e.message }); + res.status(500).json({ + error: "Falha na pipeline de agentes: " + e.message, + draft: null + }); } }); @@ -358,7 +393,10 @@ app.post('/api/agents/audit', async (req, res) => { res.json({ success: true, report: resp.text() }); } catch (e) { console.error("❌ [AGENTE ABIDOS ERROR]", e.message); - res.status(500).json({ error: e.message }); + res.status(500).json({ + error: e.message, + report: "Auditoria Indisponível: Ocorreu um erro ao comunicar com a IA." + }); } }); @@ -399,7 +437,10 @@ app.post('/api/agents/learn-style', async (req, res) => { res.json({ success: true, profile: voiceProfile }); } catch (e) { console.error("❌ [LEARN STYLE ERROR]", e.message); - res.status(500).json({ error: e.message }); + res.status(500).json({ + error: "Falha ao processar aprendizado de estilo: " + e.message, + success: false + }); } }); @@ -430,7 +471,10 @@ app.post('/api/agents/analyze-diff', async (req, res) => { res.json({ success: true, profile: voiceProfile }); } catch (e) { console.error("❌ [DIFF ANALYZE ERROR]", e.message); - res.status(500).json({ error: e.message }); + res.status(500).json({ + error: "Falha ao analisar edições: " + e.message, + success: false + }); } }); @@ -604,7 +648,13 @@ app.post('/api/chat', upload.single('screenshot'), async (req, res) => { const result = await model.generateContent({ contents: [{ role: 'user', parts }] }); const resp = await result.response; res.json({ reply: resp.text() }); - } catch (e) { res.status(500).json({ error: e.message }); } + } catch (e) { + console.error("❌ [AUDIT ERROR]", e.message); + res.status(500).json({ + error: e.message, + checklist: [] + }); + } }); app.post('/api/blueprint', async (req, res) => { @@ -639,8 +689,12 @@ app.post('/api/audit', async (req, res) => { } catch (e) { res.status(500).json({ error: e.message }); } }); -app.listen(port, () => { - console.log(`\n🚀 AntiGravity CMS: Mission Control Ativo!`); - console.log(`📡 Frontend & API rodando em http://localhost:${port}`); - console.log(`🔐 Camada de Segurança Proxy: ON`); -}); + +if (require.main === module) { + app.listen(port, () => { + console.log(`\n🚀 AntiGravity CMS: Mission Control Ativo!`); + console.log(`📡 Frontend & API rodando em http://localhost:${port}`); + console.log(`🔐 Camada de Segurança Proxy: ON`); + }); +} +module.exports = app; diff --git a/wordpress-plugin/antigravity_cors.php b/wordpress-plugin/antigravity_cors.php index 7bb1212..4adc261 100644 --- a/wordpress-plugin/antigravity_cors.php +++ b/wordpress-plugin/antigravity_cors.php @@ -30,21 +30,15 @@ $origin = get_http_origin(); // Se a requisição vier de uma das nossas URLs locais conhecidas - if (in_array($origin, $allowed_origins)) { + if (in_array($origin, $allowed_origins, true)) { header('Access-Control-Allow-Origin: ' . esc_url_raw($origin)); - } else { - // Em produção, se não estiver na lista, não enviamos o header de origem, - // o que fará o navegador bloquear a requisição por padrão. - // Para requisições GET públicas, o WP já tem comportamento padrão. - // Aqui silenciamos para evitar exposição. + header('Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT, DELETE'); + header('Access-Control-Allow-Credentials: true'); + header('Access-Control-Expose-Headers: X-WP-Total, X-WP-TotalPages, Authorization'); + // Adiciona headers críticos que o fetch no JS envia + header('Access-Control-Allow-Headers: Authorization, X-WP-Nonce, Content-Type, X-Requested-With, Application-Password'); } - header('Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT, DELETE'); - header('Access-Control-Allow-Credentials: true'); - header('Access-Control-Expose-Headers: X-WP-Total, X-WP-TotalPages, Authorization'); - // Adiciona headers críticos que o fetch no JS envia - header('Access-Control-Allow-Headers: Authorization, X-WP-Nonce, Content-Type, X-Requested-With, Application-Password'); - return $value; }); }, 15); @@ -65,7 +59,10 @@ function antigravity_expose_seo_meta() { return $object['excerpt']['rendered'] ?? ''; }, 'update_callback' => function($value, $post, $field_name) { - return wp_update_post(array('ID' => $post->ID, 'post_excerpt' => $value)); + if (!current_user_can('edit_post', $post->ID)) { + return new WP_Error('rest_forbidden', __('Sorry, you are not allowed to edit this post.', 'antigravity'), array('status' => rest_authorization_required_code())); + } + return wp_update_post(array('ID' => $post->ID, 'post_excerpt' => sanitize_textarea_field($value))); }, 'schema' => null, )); @@ -78,9 +75,13 @@ function antigravity_expose_seo_meta() { return get_post_meta($object['id'], 'rank_math_description', true); }, 'update_callback' => function($value, $post, $field_name) { + if (!current_user_can('edit_post', $post->ID)) { + return new WP_Error('rest_forbidden', __('Sorry, you are not allowed to edit this post.', 'antigravity'), array('status' => rest_authorization_required_code())); + } + $sanitized_value = sanitize_textarea_field($value); // Atualiza ambos para garantir compatibilidade caso você mude de plugin. - update_post_meta($post->ID, '_yoast_wpseo_metadesc', $value); - update_post_meta($post->ID, 'rank_math_description', $value); + update_post_meta($post->ID, '_yoast_wpseo_metadesc', $sanitized_value); + update_post_meta($post->ID, 'rank_math_description', $sanitized_value); return true; }, 'schema' => null,