digd.js/lib/store/store.json.js

168 lines
4.7 KiB
JavaScript

'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;
};