168 lines
4.7 KiB
JavaScript
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;
|
||
|
};
|