Files
sloth-manager/backend/src/db.js
T
2026-06-02 01:00:27 +02:00

171 lines
4.9 KiB
JavaScript

/**
* Simple JSON file-based cache for DNS records.
* No native dependencies — works on any Node version.
*
* Structure of dns-cache.json:
* {
* "cloudflare": {
* "zone123": {
* "synced_at": "2024-01-01T00:00:00.000Z",
* "records": [ { id, type, name, content, ttl, priority }, ... ]
* }
* }
* }
*/
const fs = require('fs');
const path = require('path');
const CACHE_PATH = process.env.DB_PATH || path.join(__dirname, '..', 'dns-cache.json');
// ─── Load / save ─────────────────────────────────────────────────────────────
function load() {
try {
return JSON.parse(fs.readFileSync(CACHE_PATH, 'utf8'));
} catch {
return {};
}
}
function save(data) {
fs.writeFileSync(CACHE_PATH, JSON.stringify(data, null, 2), 'utf8');
}
// ─── Public API ──────────────────────────────────────────────────────────────
function getRecords(provider, zoneId) {
const data = load();
return data[provider]?.[zoneId]?.records ?? [];
}
function getSyncedAt(provider, zoneId) {
const data = load();
return data[provider]?.[zoneId]?.synced_at ?? null;
}
// Returns { [zoneId]: { synced_at, record_count } } for all cached zones of a provider
function getProviderSyncStatus(provider) {
const data = load();
const zones = data[provider] ?? {};
const result = {};
for (const [zoneId, zone] of Object.entries(zones)) {
result[zoneId] = {
synced_at: zone.synced_at ?? null,
record_count: (zone.records ?? []).length,
};
}
return result;
}
function getZoneName(provider, zoneId) {
const data = load();
return data[provider]?.[zoneId]?.zone_name ?? zoneId;
}
function replaceZoneRecords(provider, zoneId, records, zoneName) {
const data = load();
if (!data[provider]) data[provider] = {};
data[provider][zoneId] = {
zone_name: zoneName ?? data[provider]?.[zoneId]?.zone_name ?? zoneId,
synced_at: new Date().toISOString(),
records: records.map(r => ({
id: r.id,
type: r.type,
name: r.name,
content: r.content,
ttl: r.ttl ?? null,
priority: r.priority ?? null,
})),
};
save(data);
}
function upsertRecord(provider, zoneId, record) {
const data = load();
if (!data[provider]) data[provider] = {};
if (!data[provider][zoneId]) data[provider][zoneId] = { synced_at: null, records: [] };
const records = data[provider][zoneId].records;
const idx = records.findIndex(r => r.id === record.id);
const entry = {
id: record.id,
type: record.type,
name: record.name,
content: record.content,
ttl: record.ttl ?? null,
priority: record.priority ?? null,
};
if (idx >= 0) records[idx] = entry;
else records.push(entry);
save(data);
}
function deleteRecord(provider, zoneId, recordId) {
const data = load();
const zone = data[provider]?.[zoneId];
if (!zone) return;
zone.records = zone.records.filter(r => r.id !== recordId);
save(data);
}
/**
* Returns all cached zones across all providers as a flat list.
* Groups duplicate zone names so you can spot the same domain on multiple providers.
*/
function getAllZones() {
const data = load();
const byName = {};
for (const [provider, zones] of Object.entries(data)) {
for (const [zoneId, zone] of Object.entries(zones)) {
const name = zone.zone_name ?? zoneId;
if (!byName[name]) byName[name] = [];
byName[name].push({
provider,
zone_id: zoneId,
record_count: (zone.records ?? []).length,
synced_at: zone.synced_at ?? null,
});
}
}
return Object.entries(byName)
.map(([name, entries]) => ({ name, entries, duplicate: entries.length > 1 }))
.sort((a, b) => a.name.localeCompare(b.name));
}
function getStats() {
const data = load();
const typeCounts = {};
let totalZones = 0;
let zonesWithMx = 0;
const perProvider = {};
for (const [provider, zones] of Object.entries(data)) {
const zoneCount = Object.keys(zones).length;
totalZones += zoneCount;
perProvider[provider] = { zones: zoneCount };
for (const zone of Object.values(zones)) {
const records = zone.records ?? [];
const hasMx = records.some(r => r.type === 'MX');
if (hasMx) zonesWithMx++;
for (const r of records) {
typeCounts[r.type] = (typeCounts[r.type] || 0) + 1;
}
}
}
const recordTypes = Object.entries(typeCounts)
.map(([type, count]) => ({ type, count }))
.sort((a, b) => b.count - a.count);
return { totalZones, zonesWithMx, recordTypes, perProvider };
}
module.exports = { getRecords, getSyncedAt, getZoneName, getProviderSyncStatus, getAllZones, replaceZoneRecords, upsertRecord, deleteRecord, getStats };