insomnia/app/database/index.js

442 lines
10 KiB
JavaScript
Raw Normal View History

import electron from 'electron';
import * as fsPath from 'path';
import * as fs from 'fs';
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 {CONTENT_TYPE_TEXT} from '../lib/contentTypes';
import {DB_PERSIST_INTERVAL, DEFAULT_SIDEBAR_WIDTH} from '../lib/constants';
2016-04-18 04:39:15 +00:00
export const TYPE_SETTINGS = 'Settings';
export const TYPE_WORKSPACE = 'Workspace';
export const TYPE_REQUEST_GROUP = 'RequestGroup';
export const TYPE_REQUEST = 'Request';
export const TYPE_RESPONSE = 'Response';
const MODEL_DEFAULTS = {
[TYPE_SETTINGS]: () => ({
showPasswords: true,
useBulkHeaderEditor: false,
followRedirects: true,
2016-07-20 23:16:28 +00:00
editorFontSize: 11,
2016-07-19 22:28:29 +00:00
editorLineWrapping: true,
timeout: -1
}),
[TYPE_WORKSPACE]: () => ({
name: 'New Workspace',
environments: [],
filter: '',
metaSidebarWidth: DEFAULT_SIDEBAR_WIDTH,
metaActiveRequestId: null
}),
[TYPE_REQUEST_GROUP]: () => ({
name: 'New Request Group',
environment: {},
metaCollapsed: false,
metaSortKey: -1 * Date.now()
}),
[TYPE_REQUEST]: () => ({
url: '',
name: 'New Request',
method: methods.METHOD_GET,
contentType: CONTENT_TYPE_TEXT,
body: '',
parameters: [],
headers: [],
authentication: {},
metaPreviewMode: PREVIEW_MODE_SOURCE,
metaSortKey: -1 * Date.now()
}),
[TYPE_RESPONSE]: () => ({
statusCode: 0,
statusMessage: '',
contentType: 'text/plain',
url: '',
bytes: 0,
millis: 0,
headers: [],
body: '',
error: ''
}),
};
let db = null;
2016-04-16 23:24:57 +00:00
2016-04-28 00:04:29 +00:00
function getDBFilePath () {
2016-04-28 07:30:26 +00:00
const basePath = electron.remote.app.getPath('userData');
return fsPath.join(basePath, 'insomnia.db.json');
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 => {
2016-04-28 00:04:29 +00:00
const dbPath = getDBFilePath();
2016-07-18 23:05:39 +00:00
2016-07-19 16:15:03 +00:00
global.db = db = {
2016-06-18 21:02:27 +00:00
created: Date.now(),
entities: {}
};
2016-07-18 23:05:39 +00:00
2016-05-11 05:43:51 +00:00
fs.readFile(getDBFilePath(), 'utf8', (err, text) => {
if (!err) {
// TODO: Better error handling
console.log('-- Restored DB from file --');
2016-07-07 20:10:55 +00:00
try {
Object.assign(db, JSON.parse(text));
} catch (e) {
console.error('Failed to parse DB file', e);
}
}
2016-05-11 05:43:51 +00:00
// Fill in the defaults
const modelTypes = Object.keys(MODEL_DEFAULTS);
modelTypes.map(t => {
db.entities[t] = db.entities[t] || {};
});
2016-05-11 05:43:51 +00:00
// Add listeners to do persistence
let timeout = null;
onChange('DB_WRITER', () => {
clearTimeout(timeout);
2016-07-16 07:22:08 +00:00
timeout = setTimeout(persistDB, DB_PERSIST_INTERVAL);
2016-05-11 05:43:51 +00:00
});
2016-06-18 21:02:27 +00:00
2016-05-11 05:43:51 +00:00
// Done
initialized = true;
console.log(`-- Initialize DB at ${dbPath} --`);
resolve();
});
})
}
2016-04-16 23:24:57 +00:00
2016-07-16 07:22:08 +00:00
function persistDB () {
// First, write to a tmp file, then overwrite the old one. This
// prevents getting a corrupted
const filePath = getDBFilePath();
const tmpFilePath = `${filePath}.tmp`;
const start = Date.now();
const blob = JSON.stringify(db, null, '\t');
const bytes = Buffer.byteLength(blob, 'utf8');
const megabytes = Math.round(bytes / 1024 / 1024 * 10) / 10;
fs.writeFile(tmpFilePath, blob, err => {
if (err) {
console.error('Failed to write DB to file', err);
} else {
fs.renameSync(tmpFilePath, filePath);
console.log(`-- Persisted DB in ${Date.now() - start}ms (${megabytes}MB) --`);
}
})
}
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];
}
export function find (type, key, value) {
const docs = [];
Object.keys(db.entities[type]).map(id => {
const doc = db.entities[type][id];
if (doc[key] === value) {
docs.push(doc);
}
});
return new Promise(resolve => resolve(docs));
}
export function get (type, id) {
2016-07-18 23:05:39 +00:00
const rawDoc = db.entities[type][id];
const modelDefaults = MODEL_DEFAULTS[type]();
2016-07-18 23:05:39 +00:00
const doc = Object.assign({}, modelDefaults, rawDoc);
return new Promise(resolve => resolve(doc));
}
function getWhere (type, key, value = '__ANY__') {
2016-06-18 21:02:27 +00:00
const ids = Object.keys(db.entities[type]);
let docs = [];
2016-07-18 23:05:39 +00:00
2016-05-11 05:43:51 +00:00
for (let i = 0; i < ids.length; i++) {
const doc = db.entities[type][ids[i]];
const modelDefaults = MODEL_DEFAULTS[type]();
if (value === '__ANY__' || doc[key] === value) {
const rawDoc = db.entities[type][ids[i]];
const doc = Object.assign({}, modelDefaults, rawDoc);
docs.push(doc);
}
2016-05-11 05:43:51 +00:00
}
return new Promise(resolve => resolve(docs));
}
function count (type) {
const count = Object.keys(db.entities[type]).length;
return new Promise(resolve => resolve(count));
}
function all (type) {
return getWhere(type);
}
2016-05-11 05:43:51 +00:00
function removeWhere (type, key, value) {
2016-06-18 21:02:27 +00:00
const ids = Object.keys(db.entities[type]);
let numRemoved = 0;
2016-05-11 05:43:51 +00:00
for (let i = 0; i < ids.length; i++) {
2016-06-18 21:02:27 +00:00
const doc = db.entities[type][ids[i]];
2016-05-11 05:43:51 +00:00
if (doc[key] === value) {
numRemoved++;
2016-05-11 05:43:51 +00:00
remove(doc);
}
}
return new Promise(resolve => resolve(numRemoved));
2016-04-16 23:24:57 +00:00
}
function insert (doc) {
2016-06-18 21:02:27 +00:00
db.entities[doc.type][doc._id] = doc;
2016-05-11 05:43:51 +00:00
Object.keys(changeListeners).map(k => changeListeners[k]('insert', doc));
return new Promise(resolve => resolve(doc));
}
function update (doc) {
return new Promise((resolve, reject) => {
// Only update if it exists in the DB
if (db.entities[doc.type][doc._id]) {
db.entities[doc.type][doc._id] = doc;
Object.keys(changeListeners).map(k => changeListeners[k]('update', doc));
resolve(doc);
} else {
reject(new Error(`Doc did not exist for update: ${doc._id}`));
}
});
}
function remove (doc) {
2016-06-18 21:02:27 +00:00
delete db.entities[doc.type][doc._id];
2016-04-29 03:37:49 +00:00
// Also remove children
Object.keys(MODEL_DEFAULTS).map(type => removeWhere(type, 'parentId', doc._id));
2016-06-18 21:02:27 +00:00
const wasLastDoc = db.entities[doc.type].length === 0;
2016-05-11 05:43:51 +00:00
Object.keys(changeListeners).map(k => changeListeners[k]('remove', doc));
return new Promise(resolve => resolve({wasLastDoc}));
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(
{},
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 baseDefaults = {
parentId: null
};
const modelDefaults = MODEL_DEFAULTS[type]();
2016-07-18 23:05:39 +00:00
const doc = Object.assign(
baseDefaults,
2016-07-18 23:05:39 +00:00
modelDefaults,
2016-04-16 23:24:57 +00:00
patch,
2016-04-18 04:39:15 +00:00
2016-04-16 23:24:57 +00:00
// Required Generated Fields
{
_id: generateId(idPrefix),
2016-04-18 04:39:15 +00:00
type: type,
2016-04-16 23:24:57 +00:00
created: Date.now(),
modified: Date.now()
}
);
2016-04-18 04:39:15 +00:00
// Fake a promise
return insert(doc);
}
// ~~~~~~~ //
// REQUEST //
// ~~~~~~~ //
export function requestCreateAndActivate (workspace, patch = {}) {
return requestCreate(patch).then(r => {
workspaceUpdate(workspace, {activeRequestId: r._id});
})
}
export function requestCopyAndActivate (workspace, request) {
return requestCopy(request).then(r => {
workspaceUpdate(workspace, {activeRequestId: 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 requestCopy (request) {
const name = `${request.name} (Copy)`;
return requestCreate(Object.assign({}, request, {name}));
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);
}
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-07-18 23:05:39 +00:00
return docCreate(TYPE_REQUEST_GROUP, 'grp', 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 getWhere(TYPE_REQUEST_GROUP, 'parentId', 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
// ~~~~~~~~~ //
// WORKSPACE //
// ~~~~~~~~~ //
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);
}
// ~~~~~~~~~ //
// WORKSPACE //
// ~~~~~~~~~ //
export function settingsCreate (patch = {}) {
return docCreate(TYPE_SETTINGS, 'sts', 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]));
}
});
}