'use strict'; function jsonDeepClone(target) { return JSON.parse( JSON.stringify(target) ); } /* init() should return an object with: { save: function -> undefined - changes to in memory representation should be persisted This could be considered the equivalent of committing a transaction to the database. primaryNameservers: { list: function -> list nameservers }, zones: { list: function -> list zones, find: function -> read zone by ???, create: update: delete: }, records: { list: function -> list records, find: function -> read record by ???, create: update: delete: } } All lists will be a deep copy of the data actually stored. */ module.exports = function init (opts) { // opts = { filepath }; var db = require(opts.filepath); var stat = require('fs').statSync(opts.filepath); var crypto = require('crypto'); // // Manual Migration // // Convert the primary nameservers from strings to objects with names and IDs. db.primaryNameservers.forEach(function (ns, i, arr) { if ('string' === typeof ns) { ns = { name: ns }; arr[i] = ns; } if (!ns.id) { ns.id = crypto.randomBytes(16).toString('hex'); } }); // Convert domains to zones and ensure that they have proper IDs and timestamps db.zones = db.zones || []; if (db.domains) { db.zones = db.zones.concat(db.domains); } 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(); } }); // Records belong to zones, but they (currently) refer to them by a zone property. // NOTE/TODO: This may pose problems where the whole list of records is not easily // filtered / kept in memory / indexed and/or retrieved by zone. Traditionally, // records are stored "within a zone" in a zone file. We may wish to have the // DB API behave more traditionally, even though some stores (like a SQL database // table) might actually store the zone as a property of a record as we currently do. db.records.forEach(function (record) { if (!record.id) { record.id = crypto.randomBytes(16).toString('hex'); } }); // Write the migrated data require('fs').writeFileSync(opts.filepath, JSON.stringify(db, null, 2)); // // End Migration // var save = function save (cb) { if (save._saving) { console.log('make pending'); save._pending.push(cb); return; } save._saving = true; // TODO: replace with something not destructive to original non-json data require('fs').writeFile(opts.filepath, JSON.stringify(db, null, 2), function (err) { console.log('done writing'); var pending = save._pending.splice(0); save._saving = false; cb(err); if (!pending.length) { return; } save(function (err) { console.log('double save'); pending.forEach(function (cb) { cb(err); }); }); }); }; save._pending = []; var dbApi = { save: function () { // hide _pending and _saving from callers var args = [].slice.call(arguments); return save.apply(null, args); }, // primaryNameservers really isn't editable - it's literally the list of FQDN's // that this database is replicated to in a multi-master fashion. // // However, lib/store/index.js does plenty to update these records in support // of the SOA records that are built from them (as does this file in the "migration" // section). I'm toying with the idea of not storing them seperately or creating the // SOA records somewhat immediately. primaryNameservers: { list: function listNameservers() { return jsonDeepClone(db.primaryNameservers); } }, zones: { list: function listZones() { return jsonDeepClone(db.zones); }, find: function getZone(predicate, cb) { var found; db.zones.some(function (z) { if (z.id && predicate.id === z.id) { found = z; return true; } if (z.name && predicate.name === z.name) { found = z; return true; } }); if (!found) { cb(null, null); return; } cb(null, jsonDeepClone(found)); return; }, create: function() {}, update: function() {}, delete: function() {} }, records: { list: function listRecords() { return jsonDeepClone(db.records); }, find: function getRecord(predicate, cb) { }, create: function() {}, update: function() {}, delete: function() {} } }; return dbApi; };