Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Expand Down
4 changes: 2 additions & 2 deletions frontend/public/js/media.js
Original file line number Diff line number Diff line change
Expand Up @@ -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())
]);

Expand Down
80 changes: 67 additions & 13 deletions frontend/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand All @@ -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';
Expand Down Expand Up @@ -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) => {
Expand All @@ -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: "<p>Erro na geração do Blueprint.</p>"
});
}
});
// 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) => {
Expand Down Expand Up @@ -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."
});
}
});

Expand Down Expand Up @@ -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
});
}
});

Expand Down Expand Up @@ -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: "<strong>Auditoria Indisponível:</strong> Ocorreu um erro ao comunicar com a IA."
});
}
});

Expand Down Expand Up @@ -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
});
}
});

Expand Down Expand Up @@ -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
});
}
});

Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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;
31 changes: 16 additions & 15 deletions wordpress-plugin/antigravity_cors.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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,
));
Expand All @@ -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,
Expand Down