314 lines
10 KiB
JavaScript
314 lines
10 KiB
JavaScript
import express from 'express';
|
|
import cors from 'cors';
|
|
import dotenv from 'dotenv';
|
|
import { query } from './db.js';
|
|
|
|
dotenv.config();
|
|
|
|
const app = express();
|
|
app.use(cors());
|
|
app.use(express.json());
|
|
|
|
// API Key da Hostinger (Fallback para env se não estiver no banco futuramente)
|
|
let HOSTINGER_API_KEY = process.env.HOSTINGER_API_KEY || '';
|
|
|
|
// --- Configuração ---
|
|
app.get('/api/config', (req, res) => {
|
|
res.json({ has_api_key: !!HOSTINGER_API_KEY });
|
|
});
|
|
|
|
app.post('/api/config/hostinger-key', (req, res) => {
|
|
const { api_key } = req.body;
|
|
if (!api_key?.trim()) return res.status(400).json({ error: 'api_key obrigatória' });
|
|
HOSTINGER_API_KEY = api_key.trim();
|
|
res.json({ ok: true });
|
|
});
|
|
|
|
// --- Domínios ---
|
|
|
|
// Listar domínios salvos no banco
|
|
app.get('/api/domains', async (req, res) => {
|
|
try {
|
|
const result = await query('SELECT * FROM domains ORDER BY name ASC');
|
|
res.json(result.rows);
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// Sincronizar domínios da Hostinger para o banco
|
|
app.post('/api/domains/sync', async (req, res) => {
|
|
if (!HOSTINGER_API_KEY) return res.status(400).json({ error: 'API key não configurada' });
|
|
|
|
try {
|
|
const r = await fetch('https://developers.hostinger.com/api/domains/v1/portfolio', {
|
|
headers: { 'Authorization': `Bearer ${HOSTINGER_API_KEY}`, 'Accept': 'application/json' }
|
|
});
|
|
|
|
if (!r.ok) {
|
|
const errText = await r.text();
|
|
return res.status(r.status).json({ error: `Hostinger Error: ${errText}` });
|
|
}
|
|
|
|
const domains = await r.json();
|
|
|
|
for (const dom of domains) {
|
|
await query(
|
|
'INSERT INTO domains (name, hostinger_id) VALUES ($1, $2) ON CONFLICT (name) DO UPDATE SET hostinger_id = $2',
|
|
[dom.domain, dom.id || null]
|
|
);
|
|
}
|
|
|
|
const result = await query('SELECT * FROM domains ORDER BY name ASC');
|
|
res.json({ ok: true, domains: result.rows });
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// --- Subdomínios ---
|
|
|
|
// Listar subdomínios de um domínio (do banco)
|
|
app.get('/api/domains/:domainId/subdomains', async (req, res) => {
|
|
try {
|
|
const result = await query(
|
|
'SELECT s.*, b.logo_url, b.primary_color, b.secondary_color, b.title, b.description FROM subdomains s LEFT JOIN branding b ON s.id = b.subdomain_id WHERE s.domain_id = $1 ORDER BY s.subdomain ASC',
|
|
[req.params.domainId]
|
|
);
|
|
res.json(result.rows);
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// Criar subdomínio (Hostinger + Banco)
|
|
app.post('/api/subdomains', async (req, res) => {
|
|
if (!HOSTINGER_API_KEY) return res.status(400).json({ error: 'API key não configurada' });
|
|
const { domainId, domainName, subdomain, type = 'A', value } = req.body;
|
|
|
|
if (!domainId || !domainName || !subdomain || !value) {
|
|
return res.status(400).json({ error: 'Campos obrigatórios ausentes' });
|
|
}
|
|
|
|
// Validação de nível único (Sem subdomínios aninhados)
|
|
const subdomainRegex = /^[a-zA-Z0-9][-a-zA-Z0-9]*$/;
|
|
if (!subdomainRegex.test(subdomain)) {
|
|
return res.status(400).json({ error: 'O subdomínio deve conter apenas letras, números e hifens, sem pontos (nível único).' });
|
|
}
|
|
|
|
try {
|
|
// 1. Criar na Hostinger
|
|
const r = await fetch(`https://developers.hostinger.com/api/dns/v1/zones/${domainName}`, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'Authorization': `Bearer ${HOSTINGER_API_KEY}`,
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
overwrite: false,
|
|
zone: [{ name: subdomain, type: type, records: [{ content: value, ttl: 300 }] }]
|
|
})
|
|
});
|
|
|
|
if (!r.ok) {
|
|
const errText = await r.text();
|
|
return res.status(r.status).json({ error: `Erro Hostinger: ${errText}` });
|
|
}
|
|
|
|
// 2. Salvar no Banco
|
|
const fullDomain = `${subdomain}.${domainName}`;
|
|
const dbResult = await query(
|
|
'INSERT INTO subdomains (domain_id, subdomain, full_domain, type, value) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (full_domain) DO UPDATE SET value = $5 RETURNING *',
|
|
[domainId, subdomain, fullDomain, type, value]
|
|
);
|
|
|
|
res.json({ ok: true, subdomain: dbResult.rows[0] });
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// Excluir Subdomínio (Hostinger + Banco)
|
|
app.delete('/api/subdomains/:id', async (req, res) => {
|
|
if (!HOSTINGER_API_KEY) return res.status(400).json({ error: 'API key não configurada' });
|
|
const { id } = req.params;
|
|
|
|
try {
|
|
// 1. Obter informações do subdomínio no banco
|
|
const subResult = await query(
|
|
`SELECT s.*, d.name as domain_name
|
|
FROM subdomains s
|
|
JOIN domains d ON s.domain_id = d.id
|
|
WHERE s.id = $1`,
|
|
[id]
|
|
);
|
|
|
|
if (subResult.rows.length === 0) {
|
|
return res.status(404).json({ error: 'Subdomínio não encontrado.' });
|
|
}
|
|
|
|
const { subdomain, domain_name, type } = subResult.rows[0];
|
|
|
|
// 2. Excluir na Hostinger
|
|
const r = await fetch(`https://developers.hostinger.com/api/dns/v1/zones/${domain_name}`, {
|
|
method: 'DELETE',
|
|
headers: {
|
|
'Authorization': `Bearer ${HOSTINGER_API_KEY}`,
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
filters: [{ name: subdomain, type: type }]
|
|
})
|
|
});
|
|
|
|
if (!r.ok) {
|
|
const errText = await r.text();
|
|
if (r.status !== 404) {
|
|
return res.status(r.status).json({ error: `Erro ao excluir na Hostinger: ${errText}` });
|
|
}
|
|
}
|
|
|
|
// 3. Excluir do Banco
|
|
await query('DELETE FROM subdomains WHERE id = $1', [id]);
|
|
|
|
res.json({ ok: true });
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// --- Gerenciamento Direto de DNS Real-Time (Hostinger) ---
|
|
|
|
// Listar registros DNS diretamente da Hostinger
|
|
app.get('/api/domains/:domainName/dns-records', async (req, res) => {
|
|
if (!HOSTINGER_API_KEY) return res.status(400).json({ error: 'API key não configurada' });
|
|
const { domainName } = req.params;
|
|
|
|
try {
|
|
const r = await fetch(`https://developers.hostinger.com/api/dns/v1/zones/${domainName}`, {
|
|
headers: { 'Authorization': `Bearer ${HOSTINGER_API_KEY}`, 'Accept': 'application/json' }
|
|
});
|
|
|
|
if (!r.ok) {
|
|
const errText = await r.text();
|
|
return res.status(r.status).json({ error: `Erro Hostinger: ${errText}` });
|
|
}
|
|
|
|
const records = await r.json();
|
|
res.json(records);
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// Criar/Adicionar registro DNS diretamente na Hostinger
|
|
app.post('/api/domains/:domainName/dns-records', async (req, res) => {
|
|
if (!HOSTINGER_API_KEY) return res.status(400).json({ error: 'API key não configurada' });
|
|
const { domainName } = req.params;
|
|
const { name, type, value, ttl = 300 } = req.body;
|
|
|
|
if (!name || !type || !value) {
|
|
return res.status(400).json({ error: 'Campos name, type e value são obrigatórios' });
|
|
}
|
|
|
|
try {
|
|
const r = await fetch(`https://developers.hostinger.com/api/dns/v1/zones/${domainName}`, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'Authorization': `Bearer ${HOSTINGER_API_KEY}`,
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
overwrite: false,
|
|
zone: [{ name, type, records: [{ content: value, ttl }] }]
|
|
})
|
|
});
|
|
|
|
if (!r.ok) {
|
|
const errText = await r.text();
|
|
return res.status(r.status).json({ error: `Erro Hostinger: ${errText}` });
|
|
}
|
|
|
|
res.json({ ok: true });
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// Deletar registro DNS diretamente na Hostinger
|
|
app.delete('/api/domains/:domainName/dns-records', async (req, res) => {
|
|
if (!HOSTINGER_API_KEY) return res.status(400).json({ error: 'API key não configurada' });
|
|
const { domainName } = req.params;
|
|
const { name, type } = req.body;
|
|
|
|
if (!name || !type) {
|
|
return res.status(400).json({ error: 'Campos name e type são obrigatórios' });
|
|
}
|
|
|
|
try {
|
|
const r = await fetch(`https://developers.hostinger.com/api/dns/v1/zones/${domainName}`, {
|
|
method: 'DELETE',
|
|
headers: {
|
|
'Authorization': `Bearer ${HOSTINGER_API_KEY}`,
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
filters: [{ name, type }]
|
|
})
|
|
});
|
|
|
|
if (!r.ok) {
|
|
const errText = await r.text();
|
|
return res.status(r.status).json({ error: `Erro Hostinger: ${errText}` });
|
|
}
|
|
|
|
res.json({ ok: true });
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// --- Branding / Identidade Visual ---
|
|
|
|
app.get('/api/branding/:subdomainId', async (req, res) => {
|
|
try {
|
|
const result = await query('SELECT * FROM branding WHERE subdomain_id = $1', [req.params.subdomainId]);
|
|
res.json(result.rows[0] || {});
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
app.put('/api/branding/:subdomainId', async (req, res) => {
|
|
const { logo_url, primary_color, secondary_color, title, description, favicon_url } = req.body;
|
|
const subdomainId = req.params.subdomainId;
|
|
|
|
try {
|
|
const result = await query(
|
|
`INSERT INTO branding (subdomain_id, logo_url, primary_color, secondary_color, title, description, favicon_url)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
ON CONFLICT (subdomain_id)
|
|
DO UPDATE SET
|
|
logo_url = EXCLUDED.logo_url,
|
|
primary_color = EXCLUDED.primary_color,
|
|
secondary_color = EXCLUDED.secondary_color,
|
|
title = EXCLUDED.title,
|
|
description = EXCLUDED.description,
|
|
favicon_url = EXCLUDED.favicon_url,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
RETURNING *`,
|
|
[subdomainId, logo_url, primary_color, secondary_color, title, description, favicon_url]
|
|
);
|
|
res.json(result.rows[0]);
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
const PORT = process.env.PORT || 8019;
|
|
app.listen(PORT, () => console.log(`[subdominio-backend] porta ${PORT}`));
|