-
-
Save AshikNesin/9e2e95918818876a996bc068e670cf00 to your computer and use it in GitHub Desktop.
Export Wallet by BudgetBakers records to JSON
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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