Skip to content

Instantly share code, notes, and snippets.

@katoen
Forked from 0x8801/export.js
Last active September 23, 2022 18:58
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save katoen/2e79f166d776cb7e2f8d0c902446af27 to your computer and use it in GitHub Desktop.
Save katoen/2e79f166d776cb7e2f8d0c902446af27 to your computer and use it in GitHub Desktop.
Export Wallet by BudgetBakers records to JSON
// Source for Jake Archibald's idb https://github.com/jakearchibald/idb/blob/v3.0.2/build/idb.js
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = global || self, factory(global.idb = {}));
}(this, function (exports) { 'use strict';
function toArray(arr) {
return Array.prototype.slice.call(arr);
}
function promisifyRequest(request) {
return new Promise(function(resolve, reject) {
request.onsuccess = function() {
resolve(request.result);
};
request.onerror = function() {
reject(request.error);
};
});
}
function promisifyRequestCall(obj, method, args) {
var request;
var p = new Promise(function(resolve, reject) {
request = obj[method].apply(obj, args);
promisifyRequest(request).then(resolve, reject);
});
p.request = request;
return p;
}
function promisifyCursorRequestCall(obj, method, args) {
var p = promisifyRequestCall(obj, method, args);
return p.then(function(value) {
if (!value) return;
return new Cursor(value, p.request);
});
}
function proxyProperties(ProxyClass, targetProp, properties) {
properties.forEach(function(prop) {
Object.defineProperty(ProxyClass.prototype, prop, {
get: function() {
return this[targetProp][prop];
},
set: function(val) {
this[targetProp][prop] = val;
}
});
});
}
function proxyRequestMethods(ProxyClass, targetProp, Constructor, properties) {
properties.forEach(function(prop) {
if (!(prop in Constructor.prototype)) return;
ProxyClass.prototype[prop] = function() {
return promisifyRequestCall(this[targetProp], prop, arguments);
};
});
}
function proxyMethods(ProxyClass, targetProp, Constructor, properties) {
properties.forEach(function(prop) {
if (!(prop in Constructor.prototype)) return;
ProxyClass.prototype[prop] = function() {
return this[targetProp][prop].apply(this[targetProp], arguments);
};
});
}
function proxyCursorRequestMethods(ProxyClass, targetProp, Constructor, properties) {
properties.forEach(function(prop) {
if (!(prop in Constructor.prototype)) return;
ProxyClass.prototype[prop] = function() {
return promisifyCursorRequestCall(this[targetProp], prop, arguments);
};
});
}
function Index(index) {
this._index = index;
}
proxyProperties(Index, '_index', [
'name',
'keyPath',
'multiEntry',
'unique'
]);
proxyRequestMethods(Index, '_index', IDBIndex, [
'get',
'getKey',
'getAll',
'getAllKeys',
'count'
]);
proxyCursorRequestMethods(Index, '_index', IDBIndex, [
'openCursor',
'openKeyCursor'
]);
function Cursor(cursor, request) {
this._cursor = cursor;
this._request = request;
}
proxyProperties(Cursor, '_cursor', [
'direction',
'key',
'primaryKey',
'value'
]);
proxyRequestMethods(Cursor, '_cursor', IDBCursor, [
'update',
'delete'
]);
// proxy 'next' methods
['advance', 'continue', 'continuePrimaryKey'].forEach(function(methodName) {
if (!(methodName in IDBCursor.prototype)) return;
Cursor.prototype[methodName] = function() {
var cursor = this;
var args = arguments;
return Promise.resolve().then(function() {
cursor._cursor[methodName].apply(cursor._cursor, args);
return promisifyRequest(cursor._request).then(function(value) {
if (!value) return;
return new Cursor(value, cursor._request);
});
});
};
});
function ObjectStore(store) {
this._store = store;
}
ObjectStore.prototype.createIndex = function() {
return new Index(this._store.createIndex.apply(this._store, arguments));
};
ObjectStore.prototype.index = function() {
return new Index(this._store.index.apply(this._store, arguments));
};
proxyProperties(ObjectStore, '_store', [
'name',
'keyPath',
'indexNames',
'autoIncrement'
]);
proxyRequestMethods(ObjectStore, '_store', IDBObjectStore, [
'put',
'add',
'delete',
'clear',
'get',
'getAll',
'getKey',
'getAllKeys',
'count'
]);
proxyCursorRequestMethods(ObjectStore, '_store', IDBObjectStore, [
'openCursor',
'openKeyCursor'
]);
proxyMethods(ObjectStore, '_store', IDBObjectStore, [
'deleteIndex'
]);
function Transaction(idbTransaction) {
this._tx = idbTransaction;
this.complete = new Promise(function(resolve, reject) {
idbTransaction.oncomplete = function() {
resolve();
};
idbTransaction.onerror = function() {
reject(idbTransaction.error);
};
idbTransaction.onabort = function() {
reject(idbTransaction.error);
};
});
}
Transaction.prototype.objectStore = function() {
return new ObjectStore(this._tx.objectStore.apply(this._tx, arguments));
};
proxyProperties(Transaction, '_tx', [
'objectStoreNames',
'mode'
]);
proxyMethods(Transaction, '_tx', IDBTransaction, [
'abort'
]);
function UpgradeDB(db, oldVersion, transaction) {
this._db = db;
this.oldVersion = oldVersion;
this.transaction = new Transaction(transaction);
}
UpgradeDB.prototype.createObjectStore = function() {
return new ObjectStore(this._db.createObjectStore.apply(this._db, arguments));
};
proxyProperties(UpgradeDB, '_db', [
'name',
'version',
'objectStoreNames'
]);
proxyMethods(UpgradeDB, '_db', IDBDatabase, [
'deleteObjectStore',
'close'
]);
function DB(db) {
this._db = db;
}
DB.prototype.transaction = function() {
return new Transaction(this._db.transaction.apply(this._db, arguments));
};
proxyProperties(DB, '_db', [
'name',
'version',
'objectStoreNames'
]);
proxyMethods(DB, '_db', IDBDatabase, [
'close'
]);
// Add cursor iterators
// TODO: remove this once browsers do the right thing with promises
['openCursor', 'openKeyCursor'].forEach(function(funcName) {
[ObjectStore, Index].forEach(function(Constructor) {
// Don't create iterateKeyCursor if openKeyCursor doesn't exist.
if (!(funcName in Constructor.prototype)) return;
Constructor.prototype[funcName.replace('open', 'iterate')] = function() {
var args = toArray(arguments);
var callback = args[args.length - 1];
var nativeObject = this._store || this._index;
var request = nativeObject[funcName].apply(nativeObject, args.slice(0, -1));
request.onsuccess = function() {
callback(request.result);
};
};
});
});
// polyfill getAll
[Index, ObjectStore].forEach(function(Constructor) {
if (Constructor.prototype.getAll) return;
Constructor.prototype.getAll = function(query, count) {
var instance = this;
var items = [];
return new Promise(function(resolve) {
instance.iterateCursor(query, function(cursor) {
if (!cursor) {
resolve(items);
return;
}
items.push(cursor.value);
if (count !== undefined && items.length == count) {
resolve(items);
return;
}
cursor.continue();
});
});
};
});
function openDb(name, version, upgradeCallback) {
var p = promisifyRequestCall(indexedDB, 'open', [name, version]);
var request = p.request;
if (request) {
request.onupgradeneeded = function(event) {
if (upgradeCallback) {
upgradeCallback(new UpgradeDB(request.result, event.oldVersion, request.transaction));
}
};
}
return p.then(function(db) {
return new DB(db);
});
}
function deleteDb(name) {
return promisifyRequestCall(indexedDB, 'deleteDatabase', [name]);
}
exports.openDb = openDb;
exports.deleteDb = deleteDb;
Object.defineProperty(exports, '__esModule', { value: true });
}));
// Get list of tables in DB
let dbs = await window.indexedDB.databases(),
db = dbs[0], // we are exporting the 1st db, change this as per your needs
dbPromise = await idb.openDb(db.name, db.version),
tableNames = [].slice.apply(dbPromise.objectStoreNames);
// Get content from DBs
let data = await Promise.all(tableNames.map(async (tableName) => {
return {
[tableName]: await dbPromise.transaction(tableName).objectStore(tableName).getAll()
}
}));
// wallet.budgetbakers.com records are stored here
let sequence = data[2]['by-sequence'];
// group all sequences by type of record. eg are 'account', 'category', 'record', 'currency', etc
// similar to _.groupBy(sequence, 'reservedModelType')
let groupedSequence = sequence.reduce((group, item) => {
if (!group[item.reservedModelType])
group[item.reservedModelType] = [];
group[item.reservedModelType].push(item)
return group;
}, {})
// Convert the category array into a map (key is category id and value is category item itself)
groupedSequence.categoryMap = groupedSequence.Category.reduce((group, item) => {
let categoryId = item._doc_id_rev.split('::')[0];
group[categoryId] = item;
return group;
}, {})
// Sort the records by date
groupedSequence.Record = groupedSequence.Record.sort((a, b) => {
return a.reservedCreatedAt.localeCompare(b.reservedCreatedAt)
})
function mapRecordWithCategoryAndAccount(item) {
let amountSign = item.type == 0 ? 1 : -1;
let account = groupedSequence.Account.find(function(acc) {
return acc._doc_id_rev.indexOf(item.accountId) > -1;
});
return {
title: item.note ? item.note : groupedSequence.categoryMap[item.categoryId].name,
payee: item.payee,
date: item.recordDate,
category: groupedSequence.categoryMap[item.categoryId].name,
amount: amountSign * item.amount/100,
account: account ? account.name : 'Other'
}
}
let records = groupedSequence.Record.map(mapRecordWithCategoryAndAccount)
//JSON.stringify(records)
// download JSON ref https://gist.github.com/eric-gm/9454490b22b3a5135fbccfe30e1ded08#gistcomment-3128675
function downloadObjectAsJson(exportObj, exportName){
var dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(exportObj));
var downloadAnchorNode = document.createElement('a');
downloadAnchorNode.setAttribute("href", dataStr);
downloadAnchorNode.setAttribute("download", exportName + ".json");
document.body.appendChild(downloadAnchorNode); // required for firefox
downloadAnchorNode.click();
downloadAnchorNode.remove();
}
downloadObjectAsJson(records, `budgetbaker-records-${+new Date()}`)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment