insomnia/app/backend/database/index.js

355 lines
8.2 KiB
JavaScript
Raw Normal View History

2016-09-21 00:03:26 +00:00
'use strict';
const electron = require('electron');
const NeDB = require('nedb');
const fsPath = require('path');
2016-09-21 20:32:45 +00:00
const {DB_PERSIST_INTERVAL} = require('../constants');
2016-09-21 00:03:26 +00:00
const {generateId} = require('../util');
const {isDevelopment} = require('../appInfo');
2016-09-21 20:32:45 +00:00
2016-09-21 00:03:26 +00:00
module.exports.CHANGE_INSERT = 'insert';
module.exports.CHANGE_UPDATE = 'update';
module.exports.CHANGE_REMOVE = 'remove';
2016-09-08 06:54:35 +00:00
2016-09-21 21:46:42 +00:00
// ~~~~~~ //
// MODELS //
// ~~~~~~ //
module.exports.stats = require('./stats');
module.exports.settings = require('./settings');
module.exports.workspace = require('./workspace');
module.exports.environment = require('./environment');
module.exports.cookieJar = require('./cookieJar');
module.exports.requestGroup = require('./requestGroup');
module.exports.request = require('./request');
module.exports.response = require('./response');
const MODELS = [
module.exports.stats,
module.exports.settings,
module.exports.workspace,
module.exports.environment,
module.exports.cookieJar,
module.exports.requestGroup,
module.exports.request,
module.exports.response
];
const MODEL_MAP = {};
module.exports.initModel = doc => Object.assign({
2016-08-15 22:31:30 +00:00
modified: Date.now(),
created: Date.now(),
parentId: null
2016-09-21 21:46:42 +00:00
}, doc);
module.exports.ALL_TYPES = MODELS.map(m => m.type);
for (const model of MODELS) {
MODEL_MAP[model.type] = model;
}
2016-09-21 00:03:26 +00:00
2016-09-21 21:46:42 +00:00
// ~~~~~~~ //
// HELPERS //
// ~~~~~~~ //
let db = null;
2016-04-16 23:24:57 +00:00
function getDBFilePath (modelType) {
// NOTE: Do not EVER change this. EVER!
2016-04-28 07:30:26 +00:00
const basePath = electron.remote.app.getPath('userData');
return fsPath.join(basePath, `insomnia.${modelType}.db`);
2016-04-28 00:04:29 +00:00
}
/**
* Initialize the database. This should be called once on app start.
* @returns {Promise}
*/
let initialized = false;
2016-09-21 00:03:26 +00:00
module.exports.initDB = (config = {}, force = false) => {
// Only init once
2016-09-08 06:54:35 +00:00
if (initialized && !force) {
2016-09-04 21:32:36 +00:00
return Promise.resolve();
}
return new Promise(resolve => {
db = {};
if (isDevelopment()) {
global.db = db;
}
2016-05-11 05:43:51 +00:00
// Fill in the defaults
2016-06-18 21:02:27 +00:00
2016-09-21 21:24:29 +00:00
module.exports.ALL_TYPES.map(t => {
const filename = getDBFilePath(t);
const autoload = true;
2016-09-04 21:32:36 +00:00
const finalConfig = Object.assign({filename, autoload}, config);
2016-05-11 05:43:51 +00:00
2016-09-04 21:32:36 +00:00
db[t] = new NeDB(finalConfig);
db[t].persistence.setAutocompactionInterval(DB_PERSIST_INTERVAL)
});
2016-07-16 07:22:08 +00:00
// Done
2016-07-16 07:22:08 +00:00
initialized = true;
console.log(`-- Initialize DB at ${getDBFilePath('t')} --`);
resolve();
});
2016-09-21 00:03:26 +00:00
};
2016-07-16 07:22:08 +00:00
let changeListeners = {};
2016-09-21 00:03:26 +00:00
module.exports.onChange = (id, callback) => {
console.log(`-- Added DB Listener ${id} -- `);
changeListeners[id] = callback;
2016-09-21 00:03:26 +00:00
};
2016-09-21 00:03:26 +00:00
module.exports.offChange = (id) => {
console.log(`-- Removed DB Listener ${id} -- `);
delete changeListeners[id];
2016-09-21 00:03:26 +00:00
};
2016-09-08 06:54:35 +00:00
function notifyOfChange (event, doc) {
Object.keys(changeListeners).map(k => changeListeners[k](event, doc));
}
2016-09-21 20:32:45 +00:00
module.exports.getMostRecentlyModified = (type, query = {}) => {
2016-09-08 06:54:35 +00:00
return new Promise(resolve => {
db[type].find(query).sort({modified: -1}).limit(1).exec((err, docs) => {
resolve(docs.length ? docs[0] : null);
})
})
2016-09-21 20:32:45 +00:00
};
2016-09-21 20:32:45 +00:00
module.exports.find = (type, query = {}) => {
return new Promise((resolve, reject) => {
db[type].find(query, (err, rawDocs) => {
if (err) {
return reject(err);
}
2016-09-21 21:46:42 +00:00
const modelDefaults = MODEL_MAP[type].init();
const docs = rawDocs.map(rawDoc => {
return Object.assign({}, modelDefaults, rawDoc);
});
resolve(docs);
});
});
2016-09-21 20:32:45 +00:00
};
2016-09-21 20:32:45 +00:00
module.exports.all = type => {
return module.exports.find(type);
};
2016-09-21 20:32:45 +00:00
module.exports.getWhere = (type, query) => {
return new Promise((resolve, reject) => {
db[type].find(query, (err, rawDocs) => {
if (err) {
return reject(err);
}
2016-05-11 05:43:51 +00:00
if (rawDocs.length === 0) {
// Not found. Too bad!
return resolve(null);
}
2016-05-11 05:43:51 +00:00
2016-09-21 21:46:42 +00:00
const modelDefaults = MODEL_MAP[type].init();
resolve(Object.assign({}, modelDefaults, rawDocs[0]));
});
});
2016-09-21 20:32:45 +00:00
};
2016-09-21 20:32:45 +00:00
module.exports.get = (type, id) => {
return module.exports.getWhere(type, {_id: id});
};
2016-09-21 20:32:45 +00:00
module.exports.count = (type, query = {}) => {
return new Promise((resolve, reject) => {
db[type].count(query, (err, count) => {
if (err) {
return reject(err);
}
2016-05-11 05:43:51 +00:00
resolve(count);
});
});
2016-09-21 20:32:45 +00:00
};
2016-04-16 23:24:57 +00:00
2016-09-21 00:03:26 +00:00
module.exports.insert = doc => {
return new Promise((resolve, reject) => {
db[doc.type].insert(doc, (err, newDoc) => {
if (err) {
return reject(err);
}
2016-06-18 21:02:27 +00:00
resolve(newDoc);
2016-09-21 00:03:26 +00:00
notifyOfChange(module.exports.CHANGE_INSERT, doc);
});
});
2016-09-21 00:03:26 +00:00
};
2016-09-21 20:32:45 +00:00
module.exports.update = doc => {
return new Promise((resolve, reject) => {
db[doc.type].update({_id: doc._id}, doc, err => {
if (err) {
return reject(err);
}
resolve(doc);
2016-09-21 00:03:26 +00:00
notifyOfChange(module.exports.CHANGE_UPDATE, doc);
});
});
2016-09-21 20:32:45 +00:00
};
2016-09-21 20:32:45 +00:00
module.exports.remove = doc => {
2016-08-15 22:31:30 +00:00
return new Promise(resolve => {
2016-09-21 00:03:26 +00:00
module.exports.withDescendants(doc).then(docs => {
2016-08-15 22:31:30 +00:00
const promises = docs.map(d => (
db[d.type].remove({_id: d._id}, {multi: true})
));
Promise.all(promises).then(() => {
2016-09-21 00:03:26 +00:00
docs.map(d => notifyOfChange(module.exports.CHANGE_REMOVE, d));
2016-08-15 22:31:30 +00:00
resolve()
});
});
});
2016-09-21 20:32:45 +00:00
};
2016-04-16 23:24:57 +00:00
/**
* Remove a lot of documents quickly and silently
*
* @param type
* @param query
* @returns {Promise.<T>}
*/
2016-09-21 20:32:45 +00:00
module.exports.removeBulkSilently = (type, query) => {
return new Promise(resolve => {
db[type].remove(query, {multi: true}, err => resolve());
});
2016-09-21 20:32:45 +00:00
};
2016-04-16 23:24:57 +00:00
// ~~~~~~~~~~~~~~~~~~~ //
// DEFAULT MODEL STUFF //
// ~~~~~~~~~~~~~~~~~~~ //
2016-04-16 23:24:57 +00:00
2016-09-21 20:32:45 +00:00
module.exports.docUpdate = (originalDoc, patch = {}) => {
const doc = Object.assign(
2016-09-21 21:46:42 +00:00
MODEL_MAP[originalDoc.type].init(),
originalDoc,
patch,
{modified: Date.now()}
);
2016-09-21 20:32:45 +00:00
return module.exports.update(doc);
};
2016-07-19 16:15:03 +00:00
2016-09-21 20:32:45 +00:00
module.exports.docCreate = (type, patch = {}) => {
2016-09-21 21:46:42 +00:00
const idPrefix = MODEL_MAP[type].prefix;
2016-09-08 06:54:35 +00:00
if (!idPrefix) {
throw new Error(`No ID prefix for ${type}`)
}
const doc = Object.assign(
2016-08-15 22:31:30 +00:00
{_id: generateId(idPrefix)},
2016-09-21 21:46:42 +00:00
MODEL_MAP[type].init(),
2016-04-16 23:24:57 +00:00
patch,
2016-04-18 04:39:15 +00:00
2016-08-15 22:31:30 +00:00
// Fields that the user can't touch
2016-04-16 23:24:57 +00:00
{
2016-04-18 04:39:15 +00:00
type: type,
2016-04-16 23:24:57 +00:00
modified: Date.now()
}
);
2016-04-18 04:39:15 +00:00
2016-09-21 00:03:26 +00:00
return module.exports.insert(doc);
2016-09-21 20:32:45 +00:00
};
2016-08-15 22:31:30 +00:00
// ~~~~~~~ //
// GENERAL //
// ~~~~~~~ //
2016-09-21 00:03:26 +00:00
module.exports.withDescendants = (doc = null) => {
2016-08-15 22:31:30 +00:00
let docsToReturn = doc ? [doc] : [];
const next = (docs) => {
const promises = [];
for (const doc of docs) {
2016-09-21 00:03:26 +00:00
for (const type of module.exports.ALL_TYPES) {
2016-08-15 22:31:30 +00:00
// If the doc is null, we want to search for parentId === null
const parentId = doc ? doc._id : null;
2016-09-21 20:32:45 +00:00
const promise = module.exports.find(type, {parentId});
promises.push(promise);
2016-08-15 22:31:30 +00:00
}
}
return Promise.all(promises).then(results => {
let newDocs = [];
2016-09-21 00:03:26 +00:00
// Gather up the docs = require(each type
2016-08-15 22:31:30 +00:00
for (const docs of results) {
for (const doc of docs) {
newDocs.push(doc);
}
}
if (newDocs.length === 0) {
// Didn't find anything. We're done
return new Promise(resolve => resolve(docsToReturn));
}
// Continue searching for children
docsToReturn = [...docsToReturn, ...newDocs];
return next(newDocs);
});
};
return next([doc]);
2016-09-21 00:03:26 +00:00
};
2016-08-15 22:31:30 +00:00
2016-09-21 00:03:26 +00:00
module.exports.duplicate = (originalDoc, patch = {}) => {
2016-09-08 06:54:35 +00:00
return new Promise((resolve, reject) => {
// 1. Copy the doc
const newDoc = Object.assign({}, originalDoc, patch);
delete newDoc._id;
delete newDoc.created;
delete newDoc.modified;
2016-09-21 20:32:45 +00:00
module.exports.docCreate(newDoc.type, newDoc).then(createdDoc => {
2016-09-08 06:54:35 +00:00
// 2. Get all the children
const promises = [];
2016-09-21 00:03:26 +00:00
for (const type of module.exports.ALL_TYPES) {
2016-09-08 06:54:35 +00:00
const parentId = originalDoc._id;
2016-09-21 20:32:45 +00:00
const promise = module.exports.find(type, {parentId});
2016-09-08 06:54:35 +00:00
promises.push(promise);
}
Promise.all(promises).then(results => {
let duplicatePromises = [];
2016-09-21 00:03:26 +00:00
// Gather up the docs = require(each type
2016-09-08 06:54:35 +00:00
for (const docs of results) {
for (const doc of docs) {
2016-09-21 00:03:26 +00:00
const promise = module.exports.duplicate(
doc,
{parentId: createdDoc._id}
);
duplicatePromises.push(promise);
2016-09-08 06:54:35 +00:00
}
}
// 3. Also duplicate all children, and recurse
Promise.all(duplicatePromises).then(() => resolve(createdDoc), reject)
})
})
})
2016-09-21 00:03:26 +00:00
};