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