insomnia/app/database/index.js

647 lines
14 KiB
JavaScript
Raw Normal View History

import electron from 'electron';
import NeDB from 'nedb';
import * as fsPath from 'path';
2016-04-27 23:33:38 +00:00
import * as methods from '../lib/constants';
import {generateId} from './util';
import {PREVIEW_MODE_SOURCE} from '../lib/previewModes';
import {DB_PERSIST_INTERVAL, DEFAULT_SIDEBAR_WIDTH} from '../lib/constants';
import {CONTENT_TYPE_TEXT} from '../lib/contentTypes';
import {isDevelopment} from '../lib/appInfo';
2016-04-18 04:39:15 +00:00
export const TYPE_STATS = 'Stats';
export const TYPE_SETTINGS = 'Settings';
export const TYPE_WORKSPACE = 'Workspace';
export const TYPE_ENVIRONMENT = 'Environment';
export const TYPE_COOKIE_JAR = 'CookieJar';
export const TYPE_REQUEST_GROUP = 'RequestGroup';
export const TYPE_REQUEST = 'Request';
export const TYPE_RESPONSE = 'Response';
2016-08-15 22:31:30 +00:00
const BASE_MODEL_DEFAULTS = () => ({
modified: Date.now(),
created: Date.now(),
parentId: null
});
const MODEL_DEFAULTS = {
[TYPE_STATS]: () => ({
lastLaunch: Date.now(),
lastVersion: null,
launches: 0
}),
[TYPE_SETTINGS]: () => ({
showPasswords: true,
useBulkHeaderEditor: false,
followRedirects: false,
2016-08-22 20:05:42 +00:00
useBulkHeaderEditor: false,
2016-07-20 23:16:28 +00:00
editorFontSize: 11,
2016-07-19 22:28:29 +00:00
editorLineWrapping: true,
httpProxy: '',
httpsProxy: '',
timeout: 0,
validateSSL: true
}),
[TYPE_WORKSPACE]: () => ({
name: 'New Workspace',
metaSidebarWidth: DEFAULT_SIDEBAR_WIDTH,
2016-08-15 22:31:30 +00:00
metaActiveEnvironmentId: null,
metaActiveRequestId: null,
metaFilter: ''
}),
[TYPE_ENVIRONMENT]: () => ({
name: 'New Environment',
data: {},
}),
[TYPE_COOKIE_JAR]: () => ({
name: 'Default Jar',
cookies: []
}),
[TYPE_REQUEST_GROUP]: () => ({
2016-07-21 19:15:35 +00:00
name: 'New Folder',
environment: {},
metaCollapsed: false,
metaSortKey: -1 * Date.now()
}),
[TYPE_REQUEST]: () => ({
url: '',
name: 'New Request',
method: methods.METHOD_GET,
body: '',
parameters: [],
headers: [{
name: 'Content-Type',
value: CONTENT_TYPE_TEXT
}],
authentication: {},
metaPreviewMode: PREVIEW_MODE_SOURCE,
metaSortKey: -1 * Date.now()
}),
[TYPE_RESPONSE]: () => ({
statusCode: 0,
statusMessage: '',
contentType: 'text/plain',
url: '',
bytesRead: 0,
elapsedTime: 0,
headers: [],
cookies: [],
body: '',
error: ''
}),
};
export const ALL_TYPES = Object.keys(MODEL_DEFAULTS);
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;
export function initDB () {
// Only init once
if (initialized) {
return new Promise(resolve => 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
const modelTypes = Object.keys(MODEL_DEFAULTS);
modelTypes.map(t => {
const filename = getDBFilePath(t);
const autoload = true;
2016-05-11 05:43:51 +00:00
db[t] = new NeDB({filename, autoload});
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-07-16 07:22:08 +00:00
}
let changeListeners = {};
export function onChange (id, callback) {
console.log(`-- Added DB Listener ${id} -- `);
changeListeners[id] = callback;
}
export function offChange (id) {
console.log(`-- Removed DB Listener ${id} -- `);
delete changeListeners[id];
}
function find (type, query = {}) {
return new Promise((resolve, reject) => {
db[type].find(query, (err, rawDocs) => {
if (err) {
return reject(err);
}
const modelDefaults = MODEL_DEFAULTS[type]();
const docs = rawDocs.map(rawDoc => {
return Object.assign({}, modelDefaults, rawDoc);
});
resolve(docs);
});
});
}
function all (type) {
return find(type);
}
function 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
const modelDefaults = MODEL_DEFAULTS[type]();
resolve(Object.assign({}, modelDefaults, rawDocs[0]));
});
});
}
function get (type, id) {
return getWhere(type, {_id: id});
}
function 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-04-16 23:24:57 +00:00
}
function 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);
Object.keys(changeListeners).map(k => changeListeners[k]('insert', doc));
});
});
}
function update (doc) {
return new Promise((resolve, reject) => {
db[doc.type].update({_id: doc._id}, doc, err => {
if (err) {
return reject(err);
}
resolve(doc);
Object.keys(changeListeners).map(k => changeListeners[k]('update', doc));
});
});
}
function remove (doc) {
2016-08-15 22:31:30 +00:00
return new Promise(resolve => {
withChildren(doc).then(docs => {
const promises = docs.map(d => (
db[d.type].remove({_id: d._id}, {multi: true})
));
Promise.all(promises).then(() => {
for (const doc of docs) {
Object.keys(changeListeners).map(k => changeListeners[k]('remove', doc));
}
resolve()
});
});
});
2016-04-16 23:24:57 +00:00
}
// ~~~~~~~~~~~~~~~~~~~ //
// DEFAULT MODEL STUFF //
// ~~~~~~~~~~~~~~~~~~~ //
2016-04-16 23:24:57 +00:00
function docUpdate (originalDoc, patch = {}) {
const doc = Object.assign(
2016-08-15 22:31:30 +00:00
BASE_MODEL_DEFAULTS(),
originalDoc,
patch,
{modified: Date.now()}
);
return update(doc);
2016-07-19 16:15:03 +00:00
}
2016-07-18 23:05:39 +00:00
function docCreate (type, idPrefix, patch = {}) {
const doc = Object.assign(
2016-08-15 22:31:30 +00:00
BASE_MODEL_DEFAULTS(),
{_id: generateId(idPrefix)},
MODEL_DEFAULTS[type](),
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
return insert(doc);
}
2016-08-15 22:31:30 +00:00
// ~~~~~~~ //
// GENERAL //
// ~~~~~~~ //
export function withChildren (doc = null) {
let docsToReturn = doc ? [doc] : [];
const next = (docs) => {
const promises = [];
for (const doc of docs) {
for (const type of ALL_TYPES) {
// If the doc is null, we want to search for parentId === null
const parentId = doc ? doc._id : null;
promises.push(find(type, {parentId}));
}
}
return Promise.all(promises).then(results => {
let newDocs = [];
// Gather up the docs from each type
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]);
}
// ~~~~~~~ //
// REQUEST //
// ~~~~~~~ //
export function requestCreateAndActivate (workspace, patch = {}) {
return requestCreate(patch).then(r => {
workspaceUpdate(workspace, {metaActiveRequestId: r._id});
})
}
export function requestCopyAndActivate (workspace, request) {
2016-08-17 21:05:14 +00:00
return requestDuplicate(request).then(r => {
workspaceUpdate(workspace, {metaActiveRequestId: r._id});
})
}
export function requestCreate (patch = {}) {
2016-07-20 23:16:28 +00:00
if (!patch.parentId) {
throw new Error('New Requests missing `parentId`', patch);
}
2016-07-18 23:05:39 +00:00
return docCreate(TYPE_REQUEST, 'req', patch);
2016-04-16 23:24:57 +00:00
}
export function requestGetById (id) {
2016-04-29 08:15:37 +00:00
return get(TYPE_REQUEST, id);
}
export function requestFindByParentId (parentId) {
return find(TYPE_REQUEST, {parentId: parentId});
}
export function requestUpdate (request, patch) {
return docUpdate(request, patch);
2016-07-19 16:15:03 +00:00
}
export function requestUpdateContentType (request, contentType) {
let headers = [...request.headers];
const contentTypeHeader = headers.find(
h => h.name.toLowerCase() === 'content-type'
);
if (!contentType) {
// Remove the contentType header if we are unsetting it
headers = headers.filter(h => h !== contentTypeHeader);
} else if (contentTypeHeader) {
contentTypeHeader.value = contentType;
} else {
headers.push({name: 'Content-Type', value: contentType})
}
return docUpdate(request, {headers});
}
2016-08-17 21:05:14 +00:00
export function requestDuplicate (request) {
const name = `${request.name} (Copy)`;
2016-08-17 21:05:14 +00:00
const newRequest = Object.assign({}, request, {name});
// Remove the old Id
delete newRequest._id;
return requestCreate(newRequest);
2016-04-16 23:24:57 +00:00
}
export function requestRemove (request) {
return remove(request);
}
export function requestAll () {
2016-05-11 05:43:51 +00:00
return all(TYPE_REQUEST);
}
export function requestGetAncestors (request) {
return new Promise(resolve => {
let ancestors = [];
const next = (doc) => {
Promise.all([
requestGroupGetById(doc.parentId),
workspaceGetById(doc.parentId)
]).then(([requestGroup, workspace]) => {
if (requestGroup) {
ancestors = [requestGroup, ...ancestors];
next(requestGroup);
} else if (workspace) {
ancestors = [workspace, ...ancestors];
next(workspace);
// We could be done here, but let's have there only be one finish case
} else {
// We're finished
resolve(ancestors);
}
});
};
next(request);
});
}
2016-04-16 23:24:57 +00:00
// ~~~~~~~~~~~~~ //
// REQUEST GROUP //
// ~~~~~~~~~~~~~ //
export function requestGroupCreate (patch = {}) {
2016-07-20 23:16:28 +00:00
if (!patch.parentId) {
throw new Error('New Requests missing `parentId`', patch);
}
2016-08-15 22:31:30 +00:00
return docCreate(TYPE_REQUEST_GROUP, 'fdr', patch);
}
2016-04-16 23:24:57 +00:00
export function requestGroupUpdate (requestGroup, patch) {
return docUpdate(requestGroup, patch);
}
export function requestGroupGetById (id) {
return get(TYPE_REQUEST_GROUP, id);
2016-07-19 16:15:03 +00:00
}
export function requestGroupFindByParentId (parentId) {
return find(TYPE_REQUEST_GROUP, {parentId});
}
export function requestGroupRemove (requestGroup) {
return remove(requestGroup);
}
export function requestGroupAll () {
2016-05-11 05:43:51 +00:00
return all(TYPE_REQUEST_GROUP);
}
2016-04-23 06:08:52 +00:00
// ~~~~~~~~ //
// RESPONSE //
2016-04-23 06:08:52 +00:00
// ~~~~~~~~ //
2016-04-16 23:24:57 +00:00
export function responseCreate (patch = {}) {
2016-07-20 23:16:28 +00:00
if (!patch.parentId) {
throw new Error('New Requests missing `parentId`', patch);
}
2016-07-18 23:05:39 +00:00
return docCreate(TYPE_RESPONSE, 'res', patch);
}
2016-04-18 04:39:15 +00:00
export function responseAll () {
2016-05-11 05:43:51 +00:00
return all(TYPE_RESPONSE);
}
2016-04-23 06:08:52 +00:00
// ~~~~~~~ //
// COOKIES //
// ~~~~~~~ //
export function cookieJarCreate (patch = {}) {
return docCreate(TYPE_COOKIE_JAR, 'jar', patch);
}
export function cookieJarGetOrCreateForWorkspace (workspace) {
const parentId = workspace._id;
return find(TYPE_COOKIE_JAR, {parentId}).then(cookieJars => {
if (cookieJars.length === 0) {
return cookieJarCreate({parentId})
} else {
return new Promise(resolve => resolve(cookieJars[0]));
}
});
}
export function cookieJarAll () {
return all(TYPE_COOKIE_JAR);
}
2016-08-15 22:31:30 +00:00
export function cookieJarGetById (id) {
return get(TYPE_COOKIE_JAR, id);
}
export function cookieJarUpdate (cookieJar, patch) {
return docUpdate(cookieJar, patch);
}
2016-04-23 06:08:52 +00:00
// ~~~~~~~~~ //
// WORKSPACE //
// ~~~~~~~~~ //
export function workspaceGetById (id) {
return get(TYPE_WORKSPACE, id);
}
2016-04-23 06:08:52 +00:00
export function workspaceCreate (patch = {}) {
2016-07-18 23:05:39 +00:00
return docCreate(TYPE_WORKSPACE, 'wrk', patch);
2016-04-23 06:08:52 +00:00
}
export function workspaceAll () {
2016-05-11 05:43:51 +00:00
return all(TYPE_WORKSPACE).then(workspaces => {
if (workspaces.length === 0) {
return workspaceCreate({name: 'Insomnia'}).then(workspaceAll);
} else {
return new Promise(resolve => resolve(workspaces))
}
});
}
export function workspaceCount () {
return count(TYPE_WORKSPACE)
}
export function workspaceUpdate (workspace, patch) {
return docUpdate(workspace, patch);
2016-07-19 16:15:03 +00:00
}
export function workspaceRemove (workspace) {
return remove(workspace);
}
// ~~~~~~~~~~~ //
// ENVIRONMENT //
// ~~~~~~~~~~~ //
export function environmentCreate (patch = {}) {
if (!patch.parentId) {
throw new Error('New Environment missing `parentId`', patch);
}
return docCreate(TYPE_ENVIRONMENT, 'env', patch);
}
export function environmentUpdate (environment, patch) {
return docUpdate(environment, patch);
}
export function environmentFindByParentId (parentId) {
return find(TYPE_ENVIRONMENT, {parentId});
}
export function environmentGetOrCreateForWorkspace (workspace) {
const parentId = workspace._id;
return find(TYPE_ENVIRONMENT, {parentId}).then(environments => {
if (environments.length === 0) {
return environmentCreate({parentId, name: 'Base Environment'})
} else {
return new Promise(resolve => resolve(environments[0]));
}
});
}
export function environmentGetById (id) {
return get(TYPE_ENVIRONMENT, id);
}
export function environmentRemove (environment) {
return remove(environment);
}
export function environmentAll () {
return all(TYPE_ENVIRONMENT);
}
// ~~~~~~~~ //
// SETTINGS //
// ~~~~~~~~ //
export function settingsCreate (patch = {}) {
return docCreate(TYPE_SETTINGS, 'set', patch);
}
export function settingsUpdate (settings, patch) {
return docUpdate(settings, patch);
}
export function settingsGet () {
return all(TYPE_SETTINGS).then(results => {
if (results.length === 0) {
return settingsCreate().then(settingsGet);
} else {
return new Promise(resolve => resolve(results[0]));
}
});
}
// ~~~~ //
// USER //
// ~~~~ //
export function statsCreate (patch = {}) {
return docCreate(TYPE_STATS, 'sta', patch);
}
export function statsUpdate (patch) {
return statsGet().then(stats => {
return docUpdate(stats, patch);
});
}
export function statsGet () {
return all(TYPE_STATS).then(results => {
if (results.length === 0) {
return statsCreate().then(statsGet);
} else {
return new Promise(resolve => resolve(results[0]));
}
});
}
export function statsIncrement (key) {
return statsGet().then(stats => {
if (stats[key] === undefined) {
throw new Error(`Stats[${key}] doesn't exist for increment`);
}
const patch = {
[key]: stats[key] + 1
};
return docUpdate(stats, patch);
});
}