digd.js/lib/store.json.js

297 lines
8.9 KiB
JavaScript

'use strict';
module.exports.create = function (opts) {
// opts = { filepath };
var engine = { db: null };
var db = require(opts.filepath);
var stat = require('fs').statSync(opts.filepath);
var crypto = require('crypto');
db.primaryNameservers.forEach(function (ns) {
if (!ns.id) {
ns.id = crypto.randomBytes(16).toString('hex');
}
});
db.zones.forEach(function (zone) {
if (!zone.name) {
zone.name = zone.id;
zone.id = null;
}
if (!zone.id) {
zone.id = crypto.randomBytes(16).toString('hex');
}
if (!zone.createdAt) { zone.createdAt = stat.mtime.valueOf(); }
if (!zone.updatedAt) { zone.updatedAt = stat.mtime.valueOf(); }
});
db.records.forEach(function (record) {
if (!record.id) {
record.id = crypto.randomBytes(16).toString('hex');
}
});
db.save = function (cb) {
if (db.save._saving) {
console.log('make pending');
db.save._pending.push(cb);
return;
}
db.save._saving = true;
require('fs').writeFile(opts.filepath, JSON.stringify(db, null, 2), function (err) {
console.log('done writing');
var pending = db.save._pending.splice(0);
db.save._saving = false;
cb(err);
if (!pending.length) {
return;
}
db.save(function (err) {
console.log('double save');
pending.forEach(function (cb) { cb(err); });
});
});
};
db.save._pending = [];
require('fs').writeFileSync(opts.filepath, JSON.stringify(db, null, 2));
engine.primaryNameservers = db.primaryNameservers;
engine.zoneToSoa = function (domain) {
var nameservers = domain.vanityNs || engine.primaryNameservers.map(function (n) { return n.name; });
var index = Math.floor(Math.random() * nameservers.length) % nameservers.length;
var nameserver = nameservers[index];
return {
id: domain.id
, name: domain.name
, typeName: 'SOA'
, className: 'IN'
, ttl: domain.ttl || 60
// nameserver -- select an NS at random if they're all in sync
, primary: nameserver
, name_server: nameserver
// admin -- email address or domain for admin
, admin: domain.admin || ('admin.' + domain.name)
, email_addr: domain.admin || ('admin.' + domain.name)
// serial -- the version, for cache-busting of secondary nameservers. suggested format: YYYYMMDDnn
, serial: domain.serial || Math.round((domain.updatedAt || domain.createdAt || 0) / 1000)
, sn: domain.serial || Math.round((domain.updatedAt || domain.createdAt || 0) / 1000)
// refresh -- only used when nameservers following the DNS NOTIFY spec talk
, refresh: domain.refresh || 1800
, ref: domain.refresh || 1800
// retry -- only used when nameservers following the DNS NOTIFY spec talk
, retry: domain.retry || 600
, ret: domain.retry || 600
// expiration -- how long other nameservers should continue when the primary goes down
, expiration: domain.expiration || 2419200
, ex: domain.expiration || 2419200
// minimum -- how long to cache a non-existent domain (also the default ttl for BIND)
, minimum: domain.minimum || 5
, nx: domain.minimum || 5
};
};
engine.peers = {
all: function (cb) {
process.nextTick(function () {
cb(null, db.primaryNameservers);
});
}
};
engine.zones = {
all: function (cb) {
process.nextTick(function () {
cb(null, db.zones.slice(0));
});
}
, get: function (queries, cb) {
if (!Array.isArray(queries)) {
queries = queries.names.map(function (n) {
return { name: n };
});
}
var myDomains = db.zones.filter(function (d) {
return queries.some(function (q) {
return d.name.toLowerCase() === q.name;
});
});
process.nextTick(function () {
cb(null, myDomains);
});
}
, touch: function (zone, cb) {
var existing;
db.zones.some(function (z) {
if (z.id && zone.id === z.id) { existing = z; return true; }
if (z.name && zone.name === z.name) { existing = z; return true; }
});
if (!existing) {
cb(null, null);
return;
}
existing.updatedAt = new Date().valueOf(); // toISOString();
console.log('touch saving...');
db.save(function (err) {
cb(err, !err && existing || null);
});
}
, save: function (zone, cb) {
if (zone.id) {
console.log('update zone!');
engine.zones.update(zone, cb);
} else {
engine.zones.create(zone, cb);
}
}
, update: function (zone, cb) {
var existing;
var dirty;
db.zones.some(function (z) {
if (z.id === zone.id) {
existing = z;
return true;
}
});
if (!existing) {
console.log('no existing zone');
cb(new Error("zone for '" + zone.id + "' does not exist"), null);
return;
}
console.log('found existing zone');
console.log(existing);
console.log(zone);
Object.keys(zone).forEach(function (key) {
var keys = [ 'name', 'id', 'revokedAt', 'changedAt', 'insertedAt', 'updatedAt', 'deletedAt' ];
if (-1 !== keys.indexOf(key)) { return; }
if (existing[key] !== zone[key]) {
dirty = true;
console.log('existing key', key, existing[key], zone[key]);
existing[key] = zone[key];
}
});
zone.updatedAt = new Date().valueOf(); // toISOString(); // Math.round(Date.now() / 1000);
if (dirty) {
zone.changedAt = zone.updatedAt;
}
console.log('saving...');
db.save(function (err) {
cb(err, !err && existing || null);
});
}
};
engine.records = {
all: function (cb) {
process.nextTick(function () {
cb(null, db.records.slice(0));
});
}
, get: function (query, cb) {
var myRecords = db.records.slice(0).filter(function (r) {
if ('string' !== typeof r.name) {
return false;
}
// TODO use IN in masterquest (or implement OR)
// Only return single-level wildcard?
if (query.name === r.name || ('*.' + query.name.split('.').slice(1).join('.')) === r.name) {
return true;
}
});
process.nextTick(function () {
cb(null, myRecords);
});
}
, save: function (record, cb) {
function touchZone(err, r) {
if (err) { cb(err); }
if (!r) { cb(null, null); }
engine.zones.touch({ name: r.zone }, cb);
}
if (record.id) {
console.log('update record!');
engine.records.update(record, touchZone);
} else {
engine.records.create(record, touchZone);
}
}
, update: function (record, cb) {
var existing;
var dirty;
db.records.some(function (r) {
if (r.id === record.id) {
existing = r;
return true;
}
});
if (!existing) {
console.log('no existing record');
cb(new Error("record for '" + record.id + "' does not exist"), null);
return;
}
console.log('found existing record');
console.log(existing);
console.log(record);
Object.keys(record).forEach(function (key) {
var keys = [ 'name', 'id', 'zone', 'revokedAt', 'changedAt', 'insertedAt', 'updatedAt', 'deletedAt' ];
if (-1 !== keys.indexOf(key)) { return; }
if (existing[key] !== record[key]) {
dirty = true;
console.log(existing[key], record[key]);
existing[key] = record[key];
}
});
record.updatedAt = new Date().valueOf(); // toISOString(); // Math.round(Date.now() / 1000);
if (dirty) {
record.changedAt = record.updatedAt;
}
console.log('saving...');
db.save(function (err) {
cb(err, !err && existing || null);
});
}
, create: function (record, cb) {
var obj = { id: crypto.randomBytes(16).toString('hex') };
console.log('found existing record');
console.log(record);
//var keys = [ 'name', 'id', 'zone', 'revokedAt', 'changedAt', 'insertedAt', 'updatedAt', 'deletedAt' ];
//var okeys = [ 'name', 'zone', 'admin', 'data', 'expiration', 'minimum', 'serial', 'retry', 'refresh', 'ttl', 'type' ]; // primary
var okeys = [ 'name', 'zone', 'type', 'data', 'class', 'ttl', 'address'
, 'exchange', 'priority', 'port', 'value', 'tag', 'flag', 'aname' ];
okeys.forEach(function (key) {
if ('undefined' !== typeof record[key]) {
obj[key] = record[key];
}
});
record.updatedAt = new Date().valueOf(); // toISOString(); // Math.round(Date.now() / 1000);
//record.changedAt = record.updatedAt;
record.insertedAt = record.updatedAt;
record.createdAt = record.updatedAt;
console.log('saving new...');
db.records.push(record);
db.save(function (err) {
cb(err, record);
});
}
};
return engine;
};