diff --git a/dist/firestore.js b/dist/firestore.js new file mode 100644 index 0000000..dac5d94 --- /dev/null +++ b/dist/firestore.js @@ -0,0 +1,142 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.backupRootCollections = exports.backupCollection = exports.backupDocument = exports.constructDocumentValue = exports.constructReferenceUrl = undefined; + +var _types = require('./types'); + +var _utility = require('./utility'); + +var _fs = require('fs'); + +var _fs2 = _interopRequireDefault(_fs); + +var _mkdirp = require('mkdirp'); + +var _mkdirp2 = _interopRequireDefault(_mkdirp); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +var constructReferenceUrl = exports.constructReferenceUrl = function constructReferenceUrl(reference) { + var referencePath = ''; + Object.keys(reference).forEach(function (key) { + Object.keys(reference[key]).forEach(function (subKey) { + if (subKey === 'segments') { + var pathArray = reference[key][subKey]; + pathArray.forEach(function (pathKey) { + referencePath = referencePath ? referencePath + '/' + pathKey : pathKey; + }); + } + }); + }); + return referencePath ? { value: referencePath, typeof: 'reference' } : referencePath; +}; + +var constructDocumentValue = exports.constructDocumentValue = function constructDocumentValue() { + var documentDataToStore = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + var keys = arguments[1]; + var documentData = arguments[2]; + + keys.forEach(function (key) { + // Boolean - boolean + // Reference - reference + // Integer - number + // Array - array + // Object - object + // Float - number + // Geographical Point - todo + // Map = todo + // Null - null + // String - string + if ((0, _types.isBoolean)(documentData[key])) { + documentDataToStore = Object.assign({}, documentDataToStore, _defineProperty({}, key, (0, _types.isBoolean)(documentData[key]))); + } else if ((0, _types.isDate)(documentData[key])) { + documentDataToStore = Object.assign({}, documentDataToStore, _defineProperty({}, key, (0, _types.isDate)(documentData[key]))); + } else if ((0, _types.isNumber)(documentData[key])) { + documentDataToStore = Object.assign({}, documentDataToStore, _defineProperty({}, key, (0, _types.isNumber)(documentData[key]))); + } else if ((0, _types.isArray)(documentData[key])) { + documentDataToStore[key] = Object.assign({}, documentDataToStore[key], { typeof: 'array' }); + documentDataToStore[key] = Object.assign({}, documentDataToStore[key], constructDocumentValue({}, Object.keys(documentData[key]), documentData[key])); + } else if ((0, _types.isObject)(documentData[key])) { + documentDataToStore[key] = Object.assign({}, documentDataToStore[key], { typeof: 'object' }); + documentDataToStore[key] = Object.assign({}, documentDataToStore[key], constructDocumentValue({}, Object.keys(documentData[key]), documentData[key])); + } else if ((0, _types.isNull)(documentData[key])) { + documentDataToStore = Object.assign({}, documentDataToStore, _defineProperty({}, key, (0, _types.isNull)(documentData[key]))); + } else if ((0, _types.isString)(documentData[key])) { + documentDataToStore = Object.assign({}, documentDataToStore, _defineProperty({}, key, (0, _types.isString)(documentData[key]))); + } else { + documentDataToStore = Object.assign({}, documentDataToStore, _defineProperty({}, key, constructReferenceUrl(documentData[key]))); + } + }); + return documentDataToStore; +}; + +var backupDocument = exports.backupDocument = function backupDocument(document, backupPath, logPath, prettyPrintJSON) { + console.log('Backing up Document \'' + logPath + document.id + '\''); + try { + _mkdirp2.default.sync(backupPath); + } catch (error) { + throw new Error('Unable to create backup path for Document \'' + document.id + '\': ' + error); + } + + var fileContents = void 0; + try { + var documentData = document.data(); + var keys = Object.keys(documentData); + var documentDataToStore = {}; + documentDataToStore = Object.assign({}, constructDocumentValue(documentDataToStore, keys, documentData)); + if (prettyPrintJSON === true) { + fileContents = JSON.stringify(documentDataToStore, null, 2); + } else { + fileContents = JSON.stringify(documentDataToStore); + } + } catch (error) { + throw new Error('Unable to serialize Document \'' + document.id + '\': ' + error); + } + try { + _fs2.default.writeFileSync(backupPath + '/' + document.id + '.json', fileContents); + } catch (error) { + throw new Error('Unable to write Document \'' + document.id + '\': ' + error); + } + + return document.ref.getCollections().then(function (collections) { + return (0, _utility.promiseSerial)(collections.map(function (collection) { + return function () { + return backupCollection(collection, backupPath + '/' + collection.id, logPath + document.id + '/', prettyPrintJSON); + }; + })); + }); +}; + +var backupCollection = exports.backupCollection = function backupCollection(collection, backupPath, logPath, prettyPrintJSON) { + console.log('Backing up Collection \'' + logPath + collection.id + '\''); + try { + _mkdirp2.default.sync(backupPath); + } catch (error) { + throw new Error('Unable to create backup path for Collection \'' + collection.id + '\': ' + error); + } + + return collection.get().then(function (documentSnapshots) { + var backupFunctions = []; + documentSnapshots.forEach(function (document) { + backupFunctions.push(function () { + return backupDocument(document, backupPath + '/' + document.id, logPath + collection.id + '/', prettyPrintJSON); + }); + }); + return (0, _utility.promiseSerial)(backupFunctions); + }); +}; + +var backupRootCollections = exports.backupRootCollections = function backupRootCollections(database, backupPath, prettyPrintJSON) { + return database.getCollections().then(function (collections) { + return (0, _utility.promiseSerial)(collections.map(function (collection) { + return function () { + return backupCollection(collection, backupPath + '/' + collection.id, '/', prettyPrintJSON); + }; + })); + }); +}; \ No newline at end of file diff --git a/dist/firestore.js.flow b/dist/firestore.js.flow new file mode 100644 index 0000000..6bdd563 --- /dev/null +++ b/dist/firestore.js.flow @@ -0,0 +1,124 @@ +/* @flow */ + +import { isString, isNull, isObject, isArray, isNumber, isDate, isBoolean } from './types' +import { promiseSerial } from './utility' + +import fs from 'fs' +import mkdirp from 'mkdirp' + +export const constructReferenceUrl = (reference: Object) => { + var referencePath = '' + Object.keys(reference).forEach(key => { + Object.keys(reference[key]).forEach(subKey => { + if (subKey === 'segments') { + const pathArray = reference[key][subKey] + pathArray.forEach(pathKey => { referencePath = referencePath ? `${referencePath}/${pathKey}` : pathKey }) + } + }) + }) + return referencePath ? { value: referencePath, typeof: 'reference' } : referencePath +} + +export const constructDocumentValue = (documentDataToStore: Object = {}, keys: Array, documentData: Object) => { + keys.forEach(key => { + // Boolean - boolean + // Reference - reference + // Integer - number + // Array - array + // Object - object + // Float - number + // Geographical Point - todo + // Map = todo + // Null - null + // String - string + if (isBoolean(documentData[key])) { + documentDataToStore = Object.assign({}, documentDataToStore, { [key]: isBoolean(documentData[key]) }) + } else if (isDate(documentData[key])) { + documentDataToStore = Object.assign({}, documentDataToStore, { [key]: isDate(documentData[key]) }) + } else if (isNumber(documentData[key])) { + documentDataToStore = Object.assign({}, documentDataToStore, { [key]: isNumber(documentData[key]) }) + } else if (isArray(documentData[key])) { + documentDataToStore[key] = Object.assign({}, documentDataToStore[key], { typeof: 'array' }) + documentDataToStore[key] = Object.assign({}, documentDataToStore[key], constructDocumentValue({}, Object.keys(documentData[key]), documentData[key])) + } else if (isObject(documentData[key])) { + documentDataToStore[key] = Object.assign({}, documentDataToStore[key], { typeof: 'object' }) + documentDataToStore[key] = Object.assign({}, documentDataToStore[key], constructDocumentValue({}, Object.keys(documentData[key]), documentData[key])) + } else if (isNull(documentData[key])) { + documentDataToStore = Object.assign({}, documentDataToStore, { [key]: isNull(documentData[key]) }) + } else if (isString(documentData[key])) { + documentDataToStore = Object.assign({}, documentDataToStore, { [key]: isString(documentData[key]) }) + } else { + documentDataToStore = Object.assign({}, documentDataToStore, { [key]: constructReferenceUrl(documentData[key]) }) + } + }) + return documentDataToStore +} + +export const backupDocument = (document: Object, backupPath: string, logPath: string, prettyPrintJSON: boolean) => { + console.log('Backing up Document \'' + logPath + document.id + '\'') + try { + mkdirp.sync(backupPath) + } catch (error) { + throw new Error('Unable to create backup path for Document \'' + document.id + '\': ' + error) + } + + let fileContents: string + try { + const documentData = document.data() + const keys = Object.keys(documentData) + var documentDataToStore = {} + documentDataToStore = Object.assign({}, constructDocumentValue(documentDataToStore, keys, documentData)) + if (prettyPrintJSON === true) { + fileContents = JSON.stringify(documentDataToStore, null, 2) + } else { + fileContents = JSON.stringify(documentDataToStore) + } + } catch (error) { + throw new Error('Unable to serialize Document \'' + document.id + '\': ' + error) + } + try { + fs.writeFileSync(backupPath + '/' + document.id + '.json', fileContents) + } catch (error) { + throw new Error('Unable to write Document \'' + document.id + '\': ' + error) + } + + return document.ref.getCollections() + .then((collections) => { + return promiseSerial(collections.map((collection) => { + return () => { + return backupCollection(collection, backupPath + '/' + collection.id, logPath + document.id + '/', prettyPrintJSON) + } + })) + }) +} + +export const backupCollection = (collection: Object, backupPath: string, logPath: string, prettyPrintJSON: boolean) => { + console.log('Backing up Collection \'' + logPath + collection.id + '\'') + try { + mkdirp.sync(backupPath) + } catch (error) { + throw new Error('Unable to create backup path for Collection \'' + collection.id + '\': ' + error) + } + + return collection.get() + .then((documentSnapshots) => { + const backupFunctions = [] + documentSnapshots.forEach((document) => { + backupFunctions.push(() => { + return backupDocument(document, backupPath + '/' + document.id, logPath + collection.id + '/', prettyPrintJSON) + }) + }) + return promiseSerial(backupFunctions) + }) +} + +export const backupRootCollections = (database: Object, backupPath: string, prettyPrintJSON: boolean) => { + return database.getCollections() + .then((collections) => { + return promiseSerial(collections.map((collection) => { + return () => { + return backupCollection(collection, backupPath + '/' + collection.id, '/', prettyPrintJSON) + } + })) + }) +} diff --git a/dist/index.js b/dist/index.js index aa154c3..980bea3 100644 --- a/dist/index.js +++ b/dist/index.js @@ -7,225 +7,6 @@ Object.defineProperty(exports, "__esModule", { var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; exports.default = function (accountCredentials, backupPath, prettyPrintJSON) { - // Returns if a value is a string - var isString = function isString(value) { - if (typeof value === 'string' || value instanceof String) { - return { - value: value, - typeof: 'string' - }; - } - return false; - }; - - // Returns if a value is really a number - var isNumber = function isNumber(value) { - if (typeof value === 'number' && isFinite(value)) { - return { - value: value, - typeof: 'number' - }; - } - return false; - }; - - // Returns if a value is an array - var isArray = function isArray(value) { - if (value && (typeof value === 'undefined' ? 'undefined' : _typeof(value)) === 'object' && value.constructor === Array) { - return { - value: value, - typeof: 'array' - }; - } - return false; - }; - - // Returns if a value is an object - var isObject = function isObject(value) { - if (value && (typeof value === 'undefined' ? 'undefined' : _typeof(value)) === 'object' && value.constructor === Object) { - return { - value: value, - typeof: 'object' - }; - } - return false; - }; - - // Returns if a value is null - var isNull = function isNull(value) { - if (value === null) { - return { - value: value, - typeof: 'null' - }; - } - return false; - }; - - // Returns if a value is undefined - var isUndefined = function isUndefined(value) { - if (typeof value === 'undefined') { - return { - value: value, - typeof: 'undefined' - }; - } - return false; - }; - - // Returns if a value is a boolean - var isBoolean = function isBoolean(value) { - if (typeof value === 'boolean') { - return { - value: value, - typeof: 'boolean' - }; - } - return false; - }; - - // Returns if value is a date object - var isDate = function isDate(value) { - if (value instanceof Date) { - return { - value: value, - typeof: 'date' - }; - } - return false; - }; - - var constructReferenceUrl = function constructReferenceUrl(reference) { - var referencePath = ''; - Object.keys(reference).forEach(function (key) { - Object.keys(reference[key]).forEach(function (subKey) { - if (subKey === 'segments') { - var pathArray = reference[key][subKey]; - pathArray.forEach(function (pathKey) { - referencePath = referencePath ? referencePath + '/' + pathKey : pathKey; - }); - } - }); - }); - return referencePath ? { value: referencePath, typeof: 'reference' } : referencePath; - }; - - var constructDocumentValue = function constructDocumentValue() { - var documentDataToStore = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - var keys = arguments[1]; - var documentData = arguments[2]; - - keys.forEach(function (key) { - // Boolean - boolean - // Reference - reference - // Integer - number - // Array - array - // Object - object - // Float - number - // Geographical Point - todo - // Map = todo - // Null - null - // String - string - if (isBoolean(documentData[key])) { - documentDataToStore = Object.assign({}, documentDataToStore, _defineProperty({}, key, isBoolean(documentData[key]))); - } else if (isDate(documentData[key])) { - documentDataToStore = Object.assign({}, documentDataToStore, _defineProperty({}, key, isDate(documentData[key]))); - } else if (isNumber(documentData[key])) { - documentDataToStore = Object.assign({}, documentDataToStore, _defineProperty({}, key, isNumber(documentData[key]))); - } else if (isArray(documentData[key])) { - documentDataToStore[key] = Object.assign({}, documentDataToStore[key], { typeof: 'array' }); - documentDataToStore[key] = Object.assign({}, documentDataToStore[key], constructDocumentValue({}, Object.keys(documentData[key]), documentData[key])); - } else if (isObject(documentData[key])) { - documentDataToStore[key] = Object.assign({}, documentDataToStore[key], { typeof: 'object' }); - documentDataToStore[key] = Object.assign({}, documentDataToStore[key], constructDocumentValue({}, Object.keys(documentData[key]), documentData[key])); - } else if (isNull(documentData[key])) { - documentDataToStore = Object.assign({}, documentDataToStore, _defineProperty({}, key, isNull(documentData[key]))); - } else if (isString(documentData[key])) { - documentDataToStore = Object.assign({}, documentDataToStore, _defineProperty({}, key, isString(documentData[key]))); - } else { - documentDataToStore = Object.assign({}, documentDataToStore, _defineProperty({}, key, constructReferenceUrl(documentData[key]))); - } - }); - return documentDataToStore; - }; - - // from: https://hackernoon.com/functional-javascript-resolving-promises-sequentially-7aac18c4431e - var promiseSerial = function promiseSerial(funcs) { - return funcs.reduce(function (promise, func) { - return promise.then(function (result) { - return func().then(function () { - return Array.prototype.concat.bind(result); - }); - }); - }, Promise.resolve([])); - }; - - var backupDocument = function backupDocument(document, backupPath, logPath) { - console.log('Backing up Document \'' + logPath + document.id + '\''); - try { - _mkdirp2.default.sync(backupPath); - } catch (error) { - throw new Error('Unable to create backup path for Document \'' + document.id + '\': ' + error); - } - - var fileContents = void 0; - try { - var documentData = document.data(); - var keys = Object.keys(documentData); - var documentDataToStore = {}; - documentDataToStore = Object.assign({}, constructDocumentValue(documentDataToStore, keys, documentData)); - if (prettyPrintJSON === true) { - fileContents = JSON.stringify(documentDataToStore, null, 2); - } else { - fileContents = JSON.stringify(documentDataToStore); - } - } catch (error) { - throw new Error('Unable to serialize Document \'' + document.id + '\': ' + error); - } - try { - _fs2.default.writeFileSync(backupPath + '/' + document.id + '.json', fileContents); - } catch (error) { - throw new Error('Unable to write Document \'' + document.id + '\': ' + error); - } - - return document.ref.getCollections().then(function (collections) { - return promiseSerial(collections.map(function (collection) { - return function () { - return backupCollection(collection, backupPath + '/' + collection.id, logPath + document.id + '/'); - }; - })); - }); - }; - - var backupCollection = function backupCollection(collection, backupPath, logPath) { - console.log('Backing up Collection \'' + logPath + collection.id + '\''); - try { - _mkdirp2.default.sync(backupPath); - } catch (error) { - throw new Error('Unable to create backup path for Collection \'' + collection.id + '\': ' + error); - } - - return collection.get().then(function (documentSnapshots) { - var backupFunctions = []; - documentSnapshots.forEach(function (document) { - backupFunctions.push(function () { - return backupDocument(document, backupPath + '/' + document.id, logPath + collection.id + '/'); - }); - }); - return promiseSerial(backupFunctions); - }); - }; - - var backupRootCollections = function backupRootCollections(database) { - return database.getCollections().then(function (collections) { - return promiseSerial(collections.map(function (collection) { - return function () { - return backupCollection(collection, backupPath + '/' + collection.id, '/'); - }; - })); - }); - }; - var accountCredentialsContents = void 0; if (typeof accountCredentials === 'string') { try { @@ -251,7 +32,7 @@ exports.default = function (accountCredentials, backupPath, prettyPrintJSON) { } var database = _firebaseAdmin2.default.firestore(); - return backupRootCollections(database); + return (0, _firestore.backupRootCollections)(database, backupPath, prettyPrintJSON); }; var _firebaseAdmin = require('firebase-admin'); @@ -266,6 +47,6 @@ var _mkdirp = require('mkdirp'); var _mkdirp2 = _interopRequireDefault(_mkdirp); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } +var _firestore = require('./firestore'); -function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } \ No newline at end of file +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } \ No newline at end of file diff --git a/dist/index.js.flow b/dist/index.js.flow index 07174a4..77dc996 100644 --- a/dist/index.js.flow +++ b/dist/index.js.flow @@ -5,224 +5,9 @@ import Firebase from 'firebase-admin' import fs from 'fs' import mkdirp from 'mkdirp' -export default function (accountCredentials: string | Object, backupPath: string, prettyPrintJSON: boolean) { - // Returns if a value is a string - const isString = (value) => { - if (typeof value === 'string' || value instanceof String) { - return { - value, - typeof: 'string' - } - } - return false - } - - // Returns if a value is really a number - const isNumber = (value) => { - if (typeof value === 'number' && isFinite(value)) { - return { - value, - typeof: 'number' - } - } - return false - } - - // Returns if a value is an array - const isArray = (value) => { - if (value && typeof value === 'object' && value.constructor === Array) { - return { - value, - typeof: 'array' - } - } - return false - } - - // Returns if a value is an object - const isObject = (value) => { - if (value && typeof value === 'object' && value.constructor === Object) { - return { - value, - typeof: 'object' - } - } - return false - } - - // Returns if a value is null - const isNull = (value) => { - if (value === null) { - return { - value, - typeof: 'null' - } - } - return false - } - - // Returns if a value is undefined - const isUndefined = (value) => { - if (typeof value === 'undefined') { - return { - value, - typeof: 'undefined' - } - } - return false - } - - // Returns if a value is a boolean - const isBoolean = (value) => { - if (typeof value === 'boolean') { - return { - value, - typeof: 'boolean' - } - } - return false - } - - // Returns if value is a date object - const isDate = (value) => { - if (value instanceof Date) { - return { - value, - typeof: 'date' - } - } - return false - } - - const constructReferenceUrl = (reference) => { - var referencePath = '' - Object.keys(reference).forEach(key => { - Object.keys(reference[key]).forEach(subKey => { - if (subKey === 'segments') { - const pathArray = reference[key][subKey] - pathArray.forEach(pathKey => { referencePath = referencePath ? `${referencePath}/${pathKey}` : pathKey }) - } - }) - }) - return referencePath ? { value: referencePath, typeof: 'reference' } : referencePath - } - - const constructDocumentValue = (documentDataToStore = {}, keys, documentData) => { - keys.forEach(key => { - // Boolean - boolean - // Reference - reference - // Integer - number - // Array - array - // Object - object - // Float - number - // Geographical Point - todo - // Map = todo - // Null - null - // String - string - if (isBoolean(documentData[key])) { - documentDataToStore = Object.assign({}, documentDataToStore, { [key]: isBoolean(documentData[key]) }) - } else if (isDate(documentData[key])) { - documentDataToStore = Object.assign({}, documentDataToStore, { [key]: isDate(documentData[key]) }) - } else if (isNumber(documentData[key])) { - documentDataToStore = Object.assign({}, documentDataToStore, { [key]: isNumber(documentData[key]) }) - } else if (isArray(documentData[key])) { - documentDataToStore[key] = Object.assign({}, documentDataToStore[key], { typeof: 'array' }) - documentDataToStore[key] = Object.assign({}, documentDataToStore[key], constructDocumentValue({}, Object.keys(documentData[key]), documentData[key])) - } else if (isObject(documentData[key])) { - documentDataToStore[key] = Object.assign({}, documentDataToStore[key], { typeof: 'object' }) - documentDataToStore[key] = Object.assign({}, documentDataToStore[key], constructDocumentValue({}, Object.keys(documentData[key]), documentData[key])) - } else if (isNull(documentData[key])) { - documentDataToStore = Object.assign({}, documentDataToStore, { [key]: isNull(documentData[key]) }) - } else if (isString(documentData[key])) { - documentDataToStore = Object.assign({}, documentDataToStore, { [key]: isString(documentData[key]) }) - } else { - documentDataToStore = Object.assign({}, documentDataToStore, { [key]: constructReferenceUrl(documentData[key]) }) - } - }) - return documentDataToStore - } - - // from: https://hackernoon.com/functional-javascript-resolving-promises-sequentially-7aac18c4431e - const promiseSerial = (funcs) => { - return funcs.reduce( - (promise, func) => { - return promise.then((result) => { - return func().then(() => { - return Array.prototype.concat.bind(result) - }) - }) - }, Promise.resolve([])) - } - - const backupDocument = (document: Object, backupPath: string, logPath: string) => { - console.log('Backing up Document \'' + logPath + document.id + '\'') - try { - mkdirp.sync(backupPath) - } catch (error) { - throw new Error('Unable to create backup path for Document \'' + document.id + '\': ' + error) - } - - let fileContents: string - try { - const documentData = document.data() - const keys = Object.keys(documentData) - var documentDataToStore = {} - documentDataToStore = Object.assign({}, constructDocumentValue(documentDataToStore, keys, documentData)) - if (prettyPrintJSON === true) { - fileContents = JSON.stringify(documentDataToStore, null, 2) - } else { - fileContents = JSON.stringify(documentDataToStore) - } - } catch (error) { - throw new Error('Unable to serialize Document \'' + document.id + '\': ' + error) - } - try { - fs.writeFileSync(backupPath + '/' + document.id + '.json', fileContents) - } catch (error) { - throw new Error('Unable to write Document \'' + document.id + '\': ' + error) - } - - return document.ref.getCollections() - .then((collections) => { - return promiseSerial(collections.map((collection) => { - return () => { - return backupCollection(collection, backupPath + '/' + collection.id, logPath + document.id + '/') - } - })) - }) - } - - const backupCollection = (collection: Object, backupPath: string, logPath: string) => { - console.log('Backing up Collection \'' + logPath + collection.id + '\'') - try { - mkdirp.sync(backupPath) - } catch (error) { - throw new Error('Unable to create backup path for Collection \'' + collection.id + '\': ' + error) - } - - return collection.get() - .then((documentSnapshots) => { - const backupFunctions = [] - documentSnapshots.forEach((document) => { - backupFunctions.push(() => { - return backupDocument(document, backupPath + '/' + document.id, logPath + collection.id + '/') - }) - }) - return promiseSerial(backupFunctions) - }) - } - - const backupRootCollections = (database: Object) => { - return database.getCollections() - .then((collections) => { - return promiseSerial(collections.map((collection) => { - return () => { - return backupCollection(collection, backupPath + '/' + collection.id, '/') - } - })) - }) - } +import { backupRootCollections } from './firestore' +export default function (accountCredentials: string | Object, backupPath: string, prettyPrintJSON: boolean) { let accountCredentialsContents: Object if (typeof accountCredentials === 'string') { try { @@ -248,5 +33,5 @@ export default function (accountCredentials: string | Object, backupPath: string } const database = Firebase.firestore() - return backupRootCollections(database) + return backupRootCollections(database, backupPath, prettyPrintJSON) } diff --git a/dist/types.js b/dist/types.js new file mode 100644 index 0000000..4161b0a --- /dev/null +++ b/dist/types.js @@ -0,0 +1,95 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + +// Returns if a value is a string +var isString = exports.isString = function isString(value) { + if (typeof value === 'string' || value instanceof String) { + return { + value: value, + type: 'string' + }; + } + return false; +}; + +// Returns if a value is really a number +var isNumber = exports.isNumber = function isNumber(value) { + if (typeof value === 'number' && isFinite(value)) { + return { + value: value, + type: 'number' + }; + } + return false; +}; + +// Returns if a value is an array +var isArray = exports.isArray = function isArray(value) { + if (value && (typeof value === 'undefined' ? 'undefined' : _typeof(value)) === 'object' && value.constructor === Array) { + return { + value: value, + type: 'array' + }; + } + return false; +}; + +// Returns if a value is an object +var isObject = exports.isObject = function isObject(value) { + if (value && (typeof value === 'undefined' ? 'undefined' : _typeof(value)) === 'object' && value.constructor === Object) { + return { + value: value, + type: 'object' + }; + } + return false; +}; + +// Returns if a value is null +var isNull = exports.isNull = function isNull(value) { + if (value === null) { + return { + value: value, + type: 'null' + }; + } + return false; +}; + +// Returns if a value is undefined +var isUndefined = exports.isUndefined = function isUndefined(value) { + if (typeof value === 'undefined') { + return { + value: value, + type: 'undefined' + }; + } + return false; +}; + +// Returns if a value is a boolean +var isBoolean = exports.isBoolean = function isBoolean(value) { + if (typeof value === 'boolean') { + return { + value: value, + type: 'boolean' + }; + } + return false; +}; + +// Returns if value is a date object +var isDate = exports.isDate = function isDate(value) { + if (value instanceof Date) { + return { + value: value, + type: 'date' + }; + } + return false; +}; \ No newline at end of file diff --git a/dist/types.js.flow b/dist/types.js.flow new file mode 100644 index 0000000..43c819e --- /dev/null +++ b/dist/types.js.flow @@ -0,0 +1,96 @@ +/* @flow */ + +export type ValueDescription = { + value: any, + type: string +} + +export type ValidationResult = ValueDescription | false; + +// Returns if a value is a string +export const isString = (value: any): ValidationResult => { + if (typeof value === 'string' || value instanceof String) { + return { + value, + type: 'string' + } + } + return false +} + +// Returns if a value is really a number +export const isNumber = (value: any): ValidationResult => { + if (typeof value === 'number' && isFinite(value)) { + return { + value, + type: 'number' + } + } + return false +} + +// Returns if a value is an array +export const isArray = (value: any): ValidationResult => { + if (value && typeof value === 'object' && value.constructor === Array) { + return { + value, + type: 'array' + } + } + return false +} + +// Returns if a value is an object +export const isObject = (value: any): ValidationResult => { + if (value && typeof value === 'object' && value.constructor === Object) { + return { + value, + type: 'object' + } + } + return false +} + +// Returns if a value is null +export const isNull = (value: any): ValidationResult => { + if (value === null) { + return { + value, + type: 'null' + } + } + return false +} + +// Returns if a value is undefined +export const isUndefined = (value: any): ValidationResult => { + if (typeof value === 'undefined') { + return { + value, + type: 'undefined' + } + } + return false +} + +// Returns if a value is a boolean +export const isBoolean = (value: any): ValidationResult => { + if (typeof value === 'boolean') { + return { + value, + type: 'boolean' + } + } + return false +} + +// Returns if value is a date object +export const isDate = (value: any): ValidationResult => { + if (value instanceof Date) { + return { + value, + type: 'date' + } + } + return false +} diff --git a/dist/utility.js b/dist/utility.js new file mode 100644 index 0000000..72508a6 --- /dev/null +++ b/dist/utility.js @@ -0,0 +1,16 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +// TODO: Flow coverage +// from: https://hackernoon.com/functional-javascript-resolving-promises-sequentially-7aac18c4431e +var promiseSerial = exports.promiseSerial = function promiseSerial(funcs) { + return funcs.reduce(function (promise, func) { + return promise.then(function (result) { + return func().then(function () { + return Array.prototype.concat.bind(result); + }); + }); + }, Promise.resolve([])); +}; \ No newline at end of file diff --git a/dist/utility.js.flow b/dist/utility.js.flow new file mode 100644 index 0000000..134ccd7 --- /dev/null +++ b/dist/utility.js.flow @@ -0,0 +1,12 @@ +// TODO: Flow coverage +// from: https://hackernoon.com/functional-javascript-resolving-promises-sequentially-7aac18c4431e +export const promiseSerial = (funcs) => { + return funcs.reduce( + (promise, func) => { + return promise.then((result) => { + return func().then(() => { + return Array.prototype.concat.bind(result) + }) + }) + }, Promise.resolve([])) +} diff --git a/lib/firestore.js b/lib/firestore.js new file mode 100644 index 0000000..6bdd563 --- /dev/null +++ b/lib/firestore.js @@ -0,0 +1,124 @@ +/* @flow */ + +import { isString, isNull, isObject, isArray, isNumber, isDate, isBoolean } from './types' +import { promiseSerial } from './utility' + +import fs from 'fs' +import mkdirp from 'mkdirp' + +export const constructReferenceUrl = (reference: Object) => { + var referencePath = '' + Object.keys(reference).forEach(key => { + Object.keys(reference[key]).forEach(subKey => { + if (subKey === 'segments') { + const pathArray = reference[key][subKey] + pathArray.forEach(pathKey => { referencePath = referencePath ? `${referencePath}/${pathKey}` : pathKey }) + } + }) + }) + return referencePath ? { value: referencePath, typeof: 'reference' } : referencePath +} + +export const constructDocumentValue = (documentDataToStore: Object = {}, keys: Array, documentData: Object) => { + keys.forEach(key => { + // Boolean - boolean + // Reference - reference + // Integer - number + // Array - array + // Object - object + // Float - number + // Geographical Point - todo + // Map = todo + // Null - null + // String - string + if (isBoolean(documentData[key])) { + documentDataToStore = Object.assign({}, documentDataToStore, { [key]: isBoolean(documentData[key]) }) + } else if (isDate(documentData[key])) { + documentDataToStore = Object.assign({}, documentDataToStore, { [key]: isDate(documentData[key]) }) + } else if (isNumber(documentData[key])) { + documentDataToStore = Object.assign({}, documentDataToStore, { [key]: isNumber(documentData[key]) }) + } else if (isArray(documentData[key])) { + documentDataToStore[key] = Object.assign({}, documentDataToStore[key], { typeof: 'array' }) + documentDataToStore[key] = Object.assign({}, documentDataToStore[key], constructDocumentValue({}, Object.keys(documentData[key]), documentData[key])) + } else if (isObject(documentData[key])) { + documentDataToStore[key] = Object.assign({}, documentDataToStore[key], { typeof: 'object' }) + documentDataToStore[key] = Object.assign({}, documentDataToStore[key], constructDocumentValue({}, Object.keys(documentData[key]), documentData[key])) + } else if (isNull(documentData[key])) { + documentDataToStore = Object.assign({}, documentDataToStore, { [key]: isNull(documentData[key]) }) + } else if (isString(documentData[key])) { + documentDataToStore = Object.assign({}, documentDataToStore, { [key]: isString(documentData[key]) }) + } else { + documentDataToStore = Object.assign({}, documentDataToStore, { [key]: constructReferenceUrl(documentData[key]) }) + } + }) + return documentDataToStore +} + +export const backupDocument = (document: Object, backupPath: string, logPath: string, prettyPrintJSON: boolean) => { + console.log('Backing up Document \'' + logPath + document.id + '\'') + try { + mkdirp.sync(backupPath) + } catch (error) { + throw new Error('Unable to create backup path for Document \'' + document.id + '\': ' + error) + } + + let fileContents: string + try { + const documentData = document.data() + const keys = Object.keys(documentData) + var documentDataToStore = {} + documentDataToStore = Object.assign({}, constructDocumentValue(documentDataToStore, keys, documentData)) + if (prettyPrintJSON === true) { + fileContents = JSON.stringify(documentDataToStore, null, 2) + } else { + fileContents = JSON.stringify(documentDataToStore) + } + } catch (error) { + throw new Error('Unable to serialize Document \'' + document.id + '\': ' + error) + } + try { + fs.writeFileSync(backupPath + '/' + document.id + '.json', fileContents) + } catch (error) { + throw new Error('Unable to write Document \'' + document.id + '\': ' + error) + } + + return document.ref.getCollections() + .then((collections) => { + return promiseSerial(collections.map((collection) => { + return () => { + return backupCollection(collection, backupPath + '/' + collection.id, logPath + document.id + '/', prettyPrintJSON) + } + })) + }) +} + +export const backupCollection = (collection: Object, backupPath: string, logPath: string, prettyPrintJSON: boolean) => { + console.log('Backing up Collection \'' + logPath + collection.id + '\'') + try { + mkdirp.sync(backupPath) + } catch (error) { + throw new Error('Unable to create backup path for Collection \'' + collection.id + '\': ' + error) + } + + return collection.get() + .then((documentSnapshots) => { + const backupFunctions = [] + documentSnapshots.forEach((document) => { + backupFunctions.push(() => { + return backupDocument(document, backupPath + '/' + document.id, logPath + collection.id + '/', prettyPrintJSON) + }) + }) + return promiseSerial(backupFunctions) + }) +} + +export const backupRootCollections = (database: Object, backupPath: string, prettyPrintJSON: boolean) => { + return database.getCollections() + .then((collections) => { + return promiseSerial(collections.map((collection) => { + return () => { + return backupCollection(collection, backupPath + '/' + collection.id, '/', prettyPrintJSON) + } + })) + }) +} diff --git a/lib/index.js b/lib/index.js index 07174a4..77dc996 100644 --- a/lib/index.js +++ b/lib/index.js @@ -5,224 +5,9 @@ import Firebase from 'firebase-admin' import fs from 'fs' import mkdirp from 'mkdirp' -export default function (accountCredentials: string | Object, backupPath: string, prettyPrintJSON: boolean) { - // Returns if a value is a string - const isString = (value) => { - if (typeof value === 'string' || value instanceof String) { - return { - value, - typeof: 'string' - } - } - return false - } - - // Returns if a value is really a number - const isNumber = (value) => { - if (typeof value === 'number' && isFinite(value)) { - return { - value, - typeof: 'number' - } - } - return false - } - - // Returns if a value is an array - const isArray = (value) => { - if (value && typeof value === 'object' && value.constructor === Array) { - return { - value, - typeof: 'array' - } - } - return false - } - - // Returns if a value is an object - const isObject = (value) => { - if (value && typeof value === 'object' && value.constructor === Object) { - return { - value, - typeof: 'object' - } - } - return false - } - - // Returns if a value is null - const isNull = (value) => { - if (value === null) { - return { - value, - typeof: 'null' - } - } - return false - } - - // Returns if a value is undefined - const isUndefined = (value) => { - if (typeof value === 'undefined') { - return { - value, - typeof: 'undefined' - } - } - return false - } - - // Returns if a value is a boolean - const isBoolean = (value) => { - if (typeof value === 'boolean') { - return { - value, - typeof: 'boolean' - } - } - return false - } - - // Returns if value is a date object - const isDate = (value) => { - if (value instanceof Date) { - return { - value, - typeof: 'date' - } - } - return false - } - - const constructReferenceUrl = (reference) => { - var referencePath = '' - Object.keys(reference).forEach(key => { - Object.keys(reference[key]).forEach(subKey => { - if (subKey === 'segments') { - const pathArray = reference[key][subKey] - pathArray.forEach(pathKey => { referencePath = referencePath ? `${referencePath}/${pathKey}` : pathKey }) - } - }) - }) - return referencePath ? { value: referencePath, typeof: 'reference' } : referencePath - } - - const constructDocumentValue = (documentDataToStore = {}, keys, documentData) => { - keys.forEach(key => { - // Boolean - boolean - // Reference - reference - // Integer - number - // Array - array - // Object - object - // Float - number - // Geographical Point - todo - // Map = todo - // Null - null - // String - string - if (isBoolean(documentData[key])) { - documentDataToStore = Object.assign({}, documentDataToStore, { [key]: isBoolean(documentData[key]) }) - } else if (isDate(documentData[key])) { - documentDataToStore = Object.assign({}, documentDataToStore, { [key]: isDate(documentData[key]) }) - } else if (isNumber(documentData[key])) { - documentDataToStore = Object.assign({}, documentDataToStore, { [key]: isNumber(documentData[key]) }) - } else if (isArray(documentData[key])) { - documentDataToStore[key] = Object.assign({}, documentDataToStore[key], { typeof: 'array' }) - documentDataToStore[key] = Object.assign({}, documentDataToStore[key], constructDocumentValue({}, Object.keys(documentData[key]), documentData[key])) - } else if (isObject(documentData[key])) { - documentDataToStore[key] = Object.assign({}, documentDataToStore[key], { typeof: 'object' }) - documentDataToStore[key] = Object.assign({}, documentDataToStore[key], constructDocumentValue({}, Object.keys(documentData[key]), documentData[key])) - } else if (isNull(documentData[key])) { - documentDataToStore = Object.assign({}, documentDataToStore, { [key]: isNull(documentData[key]) }) - } else if (isString(documentData[key])) { - documentDataToStore = Object.assign({}, documentDataToStore, { [key]: isString(documentData[key]) }) - } else { - documentDataToStore = Object.assign({}, documentDataToStore, { [key]: constructReferenceUrl(documentData[key]) }) - } - }) - return documentDataToStore - } - - // from: https://hackernoon.com/functional-javascript-resolving-promises-sequentially-7aac18c4431e - const promiseSerial = (funcs) => { - return funcs.reduce( - (promise, func) => { - return promise.then((result) => { - return func().then(() => { - return Array.prototype.concat.bind(result) - }) - }) - }, Promise.resolve([])) - } - - const backupDocument = (document: Object, backupPath: string, logPath: string) => { - console.log('Backing up Document \'' + logPath + document.id + '\'') - try { - mkdirp.sync(backupPath) - } catch (error) { - throw new Error('Unable to create backup path for Document \'' + document.id + '\': ' + error) - } - - let fileContents: string - try { - const documentData = document.data() - const keys = Object.keys(documentData) - var documentDataToStore = {} - documentDataToStore = Object.assign({}, constructDocumentValue(documentDataToStore, keys, documentData)) - if (prettyPrintJSON === true) { - fileContents = JSON.stringify(documentDataToStore, null, 2) - } else { - fileContents = JSON.stringify(documentDataToStore) - } - } catch (error) { - throw new Error('Unable to serialize Document \'' + document.id + '\': ' + error) - } - try { - fs.writeFileSync(backupPath + '/' + document.id + '.json', fileContents) - } catch (error) { - throw new Error('Unable to write Document \'' + document.id + '\': ' + error) - } - - return document.ref.getCollections() - .then((collections) => { - return promiseSerial(collections.map((collection) => { - return () => { - return backupCollection(collection, backupPath + '/' + collection.id, logPath + document.id + '/') - } - })) - }) - } - - const backupCollection = (collection: Object, backupPath: string, logPath: string) => { - console.log('Backing up Collection \'' + logPath + collection.id + '\'') - try { - mkdirp.sync(backupPath) - } catch (error) { - throw new Error('Unable to create backup path for Collection \'' + collection.id + '\': ' + error) - } - - return collection.get() - .then((documentSnapshots) => { - const backupFunctions = [] - documentSnapshots.forEach((document) => { - backupFunctions.push(() => { - return backupDocument(document, backupPath + '/' + document.id, logPath + collection.id + '/') - }) - }) - return promiseSerial(backupFunctions) - }) - } - - const backupRootCollections = (database: Object) => { - return database.getCollections() - .then((collections) => { - return promiseSerial(collections.map((collection) => { - return () => { - return backupCollection(collection, backupPath + '/' + collection.id, '/') - } - })) - }) - } +import { backupRootCollections } from './firestore' +export default function (accountCredentials: string | Object, backupPath: string, prettyPrintJSON: boolean) { let accountCredentialsContents: Object if (typeof accountCredentials === 'string') { try { @@ -248,5 +33,5 @@ export default function (accountCredentials: string | Object, backupPath: string } const database = Firebase.firestore() - return backupRootCollections(database) + return backupRootCollections(database, backupPath, prettyPrintJSON) } diff --git a/lib/types.js b/lib/types.js new file mode 100644 index 0000000..43c819e --- /dev/null +++ b/lib/types.js @@ -0,0 +1,96 @@ +/* @flow */ + +export type ValueDescription = { + value: any, + type: string +} + +export type ValidationResult = ValueDescription | false; + +// Returns if a value is a string +export const isString = (value: any): ValidationResult => { + if (typeof value === 'string' || value instanceof String) { + return { + value, + type: 'string' + } + } + return false +} + +// Returns if a value is really a number +export const isNumber = (value: any): ValidationResult => { + if (typeof value === 'number' && isFinite(value)) { + return { + value, + type: 'number' + } + } + return false +} + +// Returns if a value is an array +export const isArray = (value: any): ValidationResult => { + if (value && typeof value === 'object' && value.constructor === Array) { + return { + value, + type: 'array' + } + } + return false +} + +// Returns if a value is an object +export const isObject = (value: any): ValidationResult => { + if (value && typeof value === 'object' && value.constructor === Object) { + return { + value, + type: 'object' + } + } + return false +} + +// Returns if a value is null +export const isNull = (value: any): ValidationResult => { + if (value === null) { + return { + value, + type: 'null' + } + } + return false +} + +// Returns if a value is undefined +export const isUndefined = (value: any): ValidationResult => { + if (typeof value === 'undefined') { + return { + value, + type: 'undefined' + } + } + return false +} + +// Returns if a value is a boolean +export const isBoolean = (value: any): ValidationResult => { + if (typeof value === 'boolean') { + return { + value, + type: 'boolean' + } + } + return false +} + +// Returns if value is a date object +export const isDate = (value: any): ValidationResult => { + if (value instanceof Date) { + return { + value, + type: 'date' + } + } + return false +} diff --git a/lib/utility.js b/lib/utility.js new file mode 100644 index 0000000..134ccd7 --- /dev/null +++ b/lib/utility.js @@ -0,0 +1,12 @@ +// TODO: Flow coverage +// from: https://hackernoon.com/functional-javascript-resolving-promises-sequentially-7aac18c4431e +export const promiseSerial = (funcs) => { + return funcs.reduce( + (promise, func) => { + return promise.then((result) => { + return func().then(() => { + return Array.prototype.concat.bind(result) + }) + }) + }, Promise.resolve([])) +}