'use strict'; module.exports.create = function (opts) { // opts = { filepath }; var engine = { db: null }; var db = require(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'); } }); 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 { 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); }); } }; 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) { if (record.id) { console.log('update record!'); engine.records.update(record, cb); } else { engine.records.create(record, cb); } } , 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) { if (existing[key] !== record[key]) { dirty = true; console.log(existing[key], record[key]); existing[key] = record[key]; } }); record.updatedAt = new Date().toISOString(); // Math.round(Date.now() / 1000); if (dirty) { record.changedAt = record.updatedAt; } console.log('saving...'); db.save(function (err) { cb(err, !err && existing || null); }); } }; return engine; };