Files
subdominio.clube67.com/backend/server.js
T

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}`));