<commit_before>/*************************************************************************
 * ARMOR5 CONFIDENTIAL
 * Copyright 2012 Armor5, Inc. All Rights Reserved.
 *
 * NOTICE:  All information contained herein is, and remains the property
 * of Armor5, Inc. and its suppliers, if any. The intellectual and
 * technical concepts contained herein are proprietary to Armor5, Inc.
 * and its suppliers and may be covered by U.S. and Foreign Patents,
 * patents in process, and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material is
 * strictly forbidden unless prior written permission is obtained from
 * Armor5, Inc..
 **************************************************************************/

/**
 * Logging module. Shows time and log level (debug, info, warn, error).
 * Time is shown in milliseconds since this module was first initialized.
 * Usage:
 * 		var log = require('../lib/logger').NewLogger('logtest');
 *		log.info( 'Message: %s', 'my message');
 */

const LEVEL_ORDER = [ 'verbose', 'debug', 'info', 'warn', 'error' ];

var util = require('util');
var DateUtil = require('./dateutil');

// Static containing the time that this module was first initialized.
// Modules are loaded only once, so this will only be set once.
var t0 = (new Date()).getTime();

// Static containing stream where output should be written
var oStream = null;

// Static global holds a queue of messages that may build up while we are switching oStreams
var queue = [];

// Static global log level
var gLogLevel = 'debug';

var Logger = function (name) {

	this.moduleName = name;
	this.logLevel;

	/**
	 * Log an info message. The message can contain arguments (e.g 'Hello %s', 'world')
	 */
	this.info = function() {
		return this.log( 'info', util.format.apply(this,arguments));
	};

	this.warn = function() {
		return this.log( 'warn', util.format.apply(this,arguments));
	};

	this.debug = function() {
		return this.log( 'debug', util.format.apply(this,arguments));
	};

	this.verbose = function() {
		return this.log( 'verbose', util.format.apply(this,arguments));
	};

	this.error = function() {
		return this.log( 'error', util.format.apply(this,arguments));
	};

	this.date = function() {
		if( this.isAboveLevel('info') ) {
            var d = new Date();
            var msg = util.format( "=== CURRENT TIME  %s ====", DateUtil.toISOLocalString(d) );
            this._writeMessage( 'info', msg );
//			var logMsg = util.format( "[%s] INFO: %s - === CURRENT TIME  %s ====", DateUtil.formatMS(d.getTime()-t0,0), (this.moduleName ? this.moduleName : ""), DateUtil.toISOLocalString(d) );
//			writeMessage( logMsg );
		}
	};

	/**
	 * Output a log message. This function is suitable for providing to classes that require logging callbacks.
	 * @param level One of warn, debug, error or info
	 * @param msg The message. Cannot contain arguments.
	 */
	this.log = function( level, msg ) {
		if( this.isAboveLevel(level) ) {
            this._writeMessage( level, msg );
//			var logMsg = util.format( "[%s] %s: %s - %s", _timeDiff(), String(level).toUpperCase(), (this.moduleName ? this.moduleName : ""), msg );
//			writeMessage( logMsg );
		}
		return this;
	};

    this._writeMessage = function( level, msg ) {
        var params = { module: this.moduleName, level: level, msg: msg, timeDiff: timeDiff() };
        writeMessageParams( params );
    };

//	this.setWritePath = function( path ) {
//		var FS = require('fs');
//		oStream = FS.createWriteStream( path, { flags: 'a' } );
//		return this;
//	};

	// Not called. Placeholder in case we ever support managed shutdown
	this.destroy = function() {
		if( oStream ) {
			oStream.end();
			oStream.destroy();
		}
	};

	this.getTime = function() {
		return t0;
	};

	/**
	 * Set the log level for this object. This overrides the global log level for this object.
	 */
	this.setLogLevel = function(level) {
		this.logLevel = level;
		return this;
	}

	/**
	 * Return true if the level is equal to or greater then the reference, or if reference is null
	 */
	this.isAboveLevel = function( level ) {
		var reference = this.logLevel || gLogLevel;
		if( LEVEL_ORDER.indexOf(level) >= LEVEL_ORDER.indexOf(reference) )
			return true;
		return false;
	};

	/**
	 * Shortcut to util.format()
	 */
	this.format = function() {
		return util.format.apply(this,arguments);
	};

    oStream = oStream || require('./console')();

};

function timeDiff() {
    return (new Date()).getTime() - t0;
}

/**
 * Return an instance of the logger that has the module name set.
 */
exports.get = function(moduleName) {
	return new Logger(moduleName);
};

/**
 * Set log level globally
 */
exports.setGlobalLogLevel = function(level) {
    gLogLevel = level;
};

/**
 * Write the message. We queue messages to handle the moment in time while we are switching
 * streams and the new stream is not ready yet. We do queuing while we wait for it to be ready.
 * @param msgParams
 */
function writeMessageParams( msgParams ) {
    if( msgParams )
        queue.push( msgParams );
    if( oStream.ready ) {
        if( nextMsg = queue.shift() ) {
            oStream.write( nextMsg );
            writeMessageParams();
        }
    }
};

/**
 * Set log target. Defaults to console.
 * @param name Name of log target. Currently supports the following targets: [ 'sos' ]
 */
exports.setLogger = function( name ) {
    if( name === 'sos' && oStream.type !== 'sos' ) {
        var SOS = require('./sos');
        var oldStream = oStream;
        writeMessage( "Setting logger to SOS" );
        oStream = new SOS();
        oStream.open( onSuccess, onError, onClose );

        function onSuccess() {
            oStream.clear();
            writeMessage( "Set logger to SOS" );
        };

        function onError() {
            restoreOldStream( "Tried but failed to set logger to SOS" );
        };

        function onClose() {
            restoreOldStream( "SOS logger closed" );
        }

        function restoreOldStream( msg ) {
            if( oldStream ) {
                msg += "; restoring " + oldStream.type + " logger";
                oStream = oldStream;
                oldStream = null;
            }
            writeMessage( msg );
        }
    }

    function writeMessage( msg ) {
        var params = { module: 'logger', level: 'info', timeDiff: timeDiff(), msg: msg };
        writeMessageParams( params );
    }
};

<commit_msg>Fixed oStream init<commit_after>/*************************************************************************
 * ARMOR5 CONFIDENTIAL
 * Copyright 2012 Armor5, Inc. All Rights Reserved.
 *
 * NOTICE:  All information contained herein is, and remains the property
 * of Armor5, Inc. and its suppliers, if any. The intellectual and
 * technical concepts contained herein are proprietary to Armor5, Inc.
 * and its suppliers and may be covered by U.S. and Foreign Patents,
 * patents in process, and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material is
 * strictly forbidden unless prior written permission is obtained from
 * Armor5, Inc..
 **************************************************************************/

/**
 * Logging module. Shows time and log level (debug, info, warn, error).
 * Time is shown in milliseconds since this module was first initialized.
 * Usage:
 * 		var log = require('../lib/logger').NewLogger('logtest');
 *		log.info( 'Message: %s', 'my message');
 */

const LEVEL_ORDER = [ 'verbose', 'debug', 'info', 'warn', 'error' ];

var util = require('util');
var DateUtil = require('./dateutil');
var ConsoleStream = require('./console');

// Static containing the time that this module was first initialized.
// Modules are loaded only once, so this will only be set once.
var t0 = (new Date()).getTime();

// Static containing stream where output should be written
var oStream = new ConsoleStream();

// Static global holds a queue of messages that may build up while we are switching oStreams
var queue = [];

// Static global log level
var gLogLevel = 'debug';

var Logger = function (name) {

	this.moduleName = name;
	this.logLevel;

	/**
	 * Log an info message. The message can contain arguments (e.g 'Hello %s', 'world')
	 */
	this.info = function() {
		return this.log( 'info', util.format.apply(this,arguments));
	};

	this.warn = function() {
		return this.log( 'warn', util.format.apply(this,arguments));
	};

	this.debug = function() {
		return this.log( 'debug', util.format.apply(this,arguments));
	};

	this.verbose = function() {
		return this.log( 'verbose', util.format.apply(this,arguments));
	};

	this.error = function() {
		return this.log( 'error', util.format.apply(this,arguments));
	};

	this.date = function() {
		if( this.isAboveLevel('info') ) {
            var d = new Date();
            var msg = util.format( "=== CURRENT TIME  %s ====", DateUtil.toISOLocalString(d) );
            this._writeMessage( 'info', msg );
//			var logMsg = util.format( "[%s] INFO: %s - === CURRENT TIME  %s ====", DateUtil.formatMS(d.getTime()-t0,0), (this.moduleName ? this.moduleName : ""), DateUtil.toISOLocalString(d) );
//			writeMessage( logMsg );
		}
        return this;
	};

	/**
	 * Output a log message. This function is suitable for providing to classes that require logging callbacks.
	 * @param level One of warn, debug, error or info
	 * @param msg The message. Cannot contain arguments.
	 */
	this.log = function( level, msg ) {
		if( this.isAboveLevel(level) ) {
            this._writeMessage( level, msg );
//			var logMsg = util.format( "[%s] %s: %s - %s", _timeDiff(), String(level).toUpperCase(), (this.moduleName ? this.moduleName : ""), msg );
//			writeMessage( logMsg );
		}
		return this;
	};

    this._writeMessage = function( level, msg ) {
        var params = { module: this.moduleName, level: level, msg: msg, timeDiff: timeDiff() };
        writeMessageParams( params );
    };

//	this.setWritePath = function( path ) {
//		var FS = require('fs');
//		oStream = FS.createWriteStream( path, { flags: 'a' } );
//		return this;
//	};

	// Not called. Placeholder in case we ever support managed shutdown
	this.destroy = function() {
		if( oStream ) {
			oStream.end();
			oStream.destroy();
		}
	};

	this.getTime = function() {
		return t0;
	};

	/**
	 * Set the log level for this object. This overrides the global log level for this object.
	 */
	this.setLogLevel = function(level) {
		this.logLevel = level;
		return this;
	}

	/**
	 * Return true if the level is equal to or greater then the reference, or if reference is null
	 */
	this.isAboveLevel = function( level ) {
		var reference = this.logLevel || gLogLevel;
		if( LEVEL_ORDER.indexOf(level) >= LEVEL_ORDER.indexOf(reference) )
			return true;
		return false;
	};

	/**
	 * Shortcut to util.format()
	 */
	this.format = function() {
		return util.format.apply(this,arguments);
	};

};

function timeDiff() {
    return (new Date()).getTime() - t0;
}

/**
 * Return an instance of the logger that has the module name set.
 */
exports.get = function(moduleName) {
	return new Logger(moduleName);
};

/**
 * Set log level globally
 */
exports.setGlobalLogLevel = function(level) {
    gLogLevel = level;
};

/**
 * Write the message. We queue messages to handle the moment in time while we are switching
 * streams and the new stream is not ready yet. We do queuing while we wait for it to be ready.
 * @param msgParams
 */
function writeMessageParams( msgParams ) {
    if( msgParams )
        queue.push( msgParams );
    if( oStream.ready ) {
        if( nextMsg = queue.shift() ) {
            oStream.write( nextMsg );
            writeMessageParams();
        }
    }
};

/**
 * Set log target. Defaults to console.
 * @param name Name of log target. Currently supports the following targets: [ 'sos' ]
 */
exports.setLogger = function( name ) {
    if( name === 'sos' && oStream.type !== 'sos' ) {
        var SOS = require('./sos');
        var oldStream = oStream;
        writeMessage( "Setting logger to SOS" );
        oStream = new SOS();
        oStream.open( onSuccess, onError, onClose );

        function onSuccess() {
            oStream.clear();
            writeMessage( "Set logger to SOS" );
        };

        function onError() {
            restoreOldStream( "Tried but failed to set logger to SOS" );
        };

        function onClose() {
            restoreOldStream( "SOS logger closed" );
        }

        function restoreOldStream( msg ) {
            if( oldStream ) {
                msg += "; restoring " + oldStream.type + " logger";
                oStream = oldStream;
                oldStream = null;
            }
            writeMessage( msg );
        }
    }

    function writeMessage( msg ) {
        var params = { module: 'logger', level: 'info', timeDiff: timeDiff(), msg: msg };
        writeMessageParams( params );
    }
};

<|endoftext|>


<commit_before>class DBManager {

  displayOfflineAlert() {
    var message = "There was an issue loading your offline database. This could happen for two reasons:";
    message += "\n\n1. You're in a private window in your browser. We can't save your data without access to the local database. Please use a non-private window.";
    message += "\n\n2. You have two windows of the app open at the same time. Please close any other app instances and reload the page.";
    alert(message);
  }

  openDatabase(callback, onUgradeNeeded) {
    var request = window.indexedDB.open("standardnotes", 1);

    request.onerror = function(event) {
      if(event.target.errorCode) {
        alert("Offline database issue: " + event.target.errorCode);
      } else {
        this.displayOfflineAlert();
      }
      console.error("Offline database issue:", event);
      if(callback) {
        callback(null);
      }
    }.bind(this);

    request.onsuccess = (event) => {
      var db = event.target.result;
      db.onversionchange = function(event) {
          db.close();
      };
      db.onerror = function(errorEvent) {
        console.log("Database error: " + errorEvent.target.errorCode);
      }
      if(callback) {
        callback(db);
      }
    };

    request.onupgradeneeded = (event) => {
      console.log("Upgrade needed", event);
      var db = event.target.result;
      if(db.version === 1) {
        if(onUgradeNeeded) {
          onUgradeNeeded();
        }
      }

      // Create an objectStore for this database
      var objectStore = db.createObjectStore("items", { keyPath: "uuid" });
      objectStore.createIndex("title", "title", { unique: false });
      objectStore.createIndex("uuid", "uuid", { unique: true });
      objectStore.transaction.oncomplete = function(event) {
        // Ready to store values in the newly created objectStore.
      };
    };
  }

  getAllItems(callback) {
    this.openDatabase((db) => {
      var objectStore = db.transaction("items").objectStore("items");
      var items = [];
      objectStore.openCursor().onsuccess = function(event) {
        var cursor = event.target.result;
        if (cursor) {
          items.push(cursor.value);
          cursor.continue();
        }
        else {
          callback(items);
        }
      };
    }, null)
  }

  saveItem(item) {
    this.saveItems([item]);
  }

  saveItems(items, callback) {

    if(items.length == 0) {
      if(callback) {
        callback();
      }
      return;
    }

    this.openDatabase((db) => {
      var transaction = db.transaction("items", "readwrite");
      transaction.oncomplete = function(event) {

      };

      transaction.onerror = function(event) {
        console.log("Transaction error:", event.target.errorCode);
      };

      var itemObjectStore = transaction.objectStore("items");
      var i = 0;
      putNext();

      function putNext() {
        if (i < items.length) {
          var item = items[i];
          itemObjectStore.put(item).onsuccess = putNext;
          ++i;
        } else {
          if(callback){
            callback();
          }
        }
      }
    }, null)
  }

  deleteItem(item, callback) {
    this.openDatabase((db) => {
      var request = db.transaction("items", "readwrite").objectStore("items").delete(item.uuid);
      request.onsuccess = function(event) {
        if(callback) {
          callback(true);
        }
      };
    }, null)
  }

  getItemByUUID(uuid, callback) {
    this.openDatabase((db) => {
      var request = db.transaction("items", "readonly").objectStore("items").get(uuid);
      request.onsuccess = function(event) {
        callback(event.result);
      };
    }, null);
  }

  clearAllItems(callback) {
    var deleteRequest = window.indexedDB.deleteDatabase("standardnotes");

    deleteRequest.onerror = function(event) {
      console.log("Error deleting database.");
      callback();
    };
    deleteRequest.onsuccess = function(event) {
      console.log("Database deleted successfully");
      callback();
    };
    deleteRequest.onblock = function(event) {
      callback();
    };
  }
}

angular.module('app.frontend').service('dbManager', DBManager);
<commit_msg>Handle DB delete onblocked<commit_after>class DBManager {

  displayOfflineAlert() {
    var message = "There was an issue loading your offline database. This could happen for two reasons:";
    message += "\n\n1. You're in a private window in your browser. We can't save your data without access to the local database. Please use a non-private window.";
    message += "\n\n2. You have two windows of the app open at the same time. Please close any other app instances and reload the page.";
    alert(message);
  }

  openDatabase(callback, onUgradeNeeded) {
    var request = window.indexedDB.open("standardnotes", 1);

    request.onerror = function(event) {
      if(event.target.errorCode) {
        alert("Offline database issue: " + event.target.errorCode);
      } else {
        this.displayOfflineAlert();
      }
      console.error("Offline database issue:", event);
      if(callback) {
        callback(null);
      }
    }.bind(this);

    request.onsuccess = (event) => {
      var db = event.target.result;
      db.onversionchange = function(event) {
        db.close();
      };
      db.onerror = function(errorEvent) {
        console.log("Database error: " + errorEvent.target.errorCode);
      }
      if(callback) {
        callback(db);
      }
    };

    request.onupgradeneeded = (event) => {
      var db = event.target.result;

      db.onversionchange = function(event) {
        db.close();
      };

      // Create an objectStore for this database
      var objectStore = db.createObjectStore("items", { keyPath: "uuid" });
      objectStore.createIndex("title", "title", { unique: false });
      objectStore.createIndex("uuid", "uuid", { unique: true });
      objectStore.transaction.oncomplete = function(event) {
        // Ready to store values in the newly created objectStore.
        if(db.version === 1) {
          if(onUgradeNeeded) {
            onUgradeNeeded();
          }
        }
      };
    };
  }

  getAllItems(callback) {
    this.openDatabase((db) => {
      var objectStore = db.transaction("items").objectStore("items");
      var items = [];
      objectStore.openCursor().onsuccess = function(event) {
        var cursor = event.target.result;
        if (cursor) {
          items.push(cursor.value);
          cursor.continue();
        }
        else {
          callback(items);
        }
      };
    }, null)
  }

  saveItem(item) {
    this.saveItems([item]);
  }

  saveItems(items, callback) {

    if(items.length == 0) {
      if(callback) {
        callback();
      }
      return;
    }

    this.openDatabase((db) => {
      var transaction = db.transaction("items", "readwrite");
      transaction.oncomplete = function(event) {

      };

      transaction.onerror = function(event) {
        console.log("Transaction error:", event.target.errorCode);
      };

      var itemObjectStore = transaction.objectStore("items");
      var i = 0;
      putNext();

      function putNext() {
        if (i < items.length) {
          var item = items[i];
          itemObjectStore.put(item).onsuccess = putNext;
          ++i;
        } else {
          if(callback){
            callback();
          }
        }
      }
    }, null)
  }

  deleteItem(item, callback) {
    this.openDatabase((db) => {
      var request = db.transaction("items", "readwrite").objectStore("items").delete(item.uuid);
      request.onsuccess = function(event) {
        if(callback) {
          callback(true);
        }
      };
    }, null)
  }

  getItemByUUID(uuid, callback) {
    this.openDatabase((db) => {
      var request = db.transaction("items", "readonly").objectStore("items").get(uuid);
      request.onsuccess = function(event) {
        callback(event.result);
      };
    }, null);
  }

  clearAllItems(callback) {
    var deleteRequest = window.indexedDB.deleteDatabase("standardnotes");

    deleteRequest.onerror = function(event) {
      console.log("Error deleting database.");
      callback();
    };

    deleteRequest.onsuccess = function(event) {
      console.log("Database deleted successfully");
      callback();
    };

    deleteRequest.onblocked = function(event) {
      console.error("Delete request blocked");
      alert("Your browser is blocking Standard Notes from deleting the local database. Make sure there are no other open windows of this app and try again. If the issue persists, please manually delete app data to sign out.")
    };
  }
}

angular.module('app.frontend').service('dbManager', DBManager);
<|endoftext|>


<commit_before>/**
 * @author Gilles Coomans <gilles.coomans@gmail.com>
 * @licence MIT
 * Minimalistic, workable, understandable html5 history shim.
 * IE8+, FF3+, Safari, Chrome, ...
 *
 *
 * Remarque : for IE8, please use it in conjonction with https://github.com/WebReflection/ie8
 * (Events listener shim for IE8)
 *
 * TODO :
 * 	test in IE8
 * 	work on creatEvent shim
 *
 * 	write test
 *
 * manage basepath
 *
 * manage title
 *   ==> as all browser ignore it :
 *   	=> need to manage it through store even for native.
 *   	=> should use hid in data object to retreieve it on popstate
 *
 * manage internal/external flag
 */
(function() {
	'use strict';
	if (!window.history) // too old !!! ;)
		return;

	// ___________________________________________________________ UTILS
	var _uaMatch = function(ua) {
		ua = ua.toLowerCase();
		var match = /(chrome)[ \/]([\w.]+)/.exec(ua) ||
			/(webkit)[ \/]([\w.]+)/.exec(ua) ||
			/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) ||
			/(msie) ([\w.]+)/.exec(ua) ||
			ua.indexOf('compatible') < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || [];
		return {
			browser: match[1] || '',
			version: match[2] || '0'
		};
	};

	var userAgent = function() {
		var browser = {},
			matched = _uaMatch(navigator.userAgent);
		if (matched.browser) {
			browser[matched.browser] = true;
			browser.version = matched.version;
		}
		if (browser.chrome)
			browser.webkit = true;
		else if (browser.webkit)
			browser.safari = true;
		return browser;
	};

	var parseUri = function(str) {
		var o = parseUri.options,
			m = o.parser[o.strictMode ? "strict" : "loose"].exec(str),
			uri = {},
			i = 14;
		while (i--)
			uri[o.key[i]] = m[i] || "";
		uri[o.q.name] = {};
		uri[o.key[12]].replace(o.q.parser, function($0, $1, $2) {
			if ($1) uri[o.q.name][$1] = $2;
		});
		return uri;
	};

	parseUri.options = {
		strictMode: true,
		key: ["href", "protocol", "host", "userInfo", "user", "password", "hostname", "port", "relative", "pathname", "directory", "file", "search", "hash"],
		q: {
			name: "query",
			parser: /(?:^|&)([^&=]*)=?([^&]*)/g
		},
		parser: {
			strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
			loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
		}
	};

	var parseURL = function(url) {
		var obj = parseUri(url);
		obj.path = obj.pathname + (obj.search?"?":"") + obj.search;
		return obj;
	};

	// http://stackoverflow.com/questions/9077101/iphone-localstorage-quota-exceeded-err-issue/12976988#12976988
	var sessionStorageSupported = function() {
		try {
			sessionStorage.setItem("test", "test");
			sessionStorage.removeItem("test");
			return true;
		} catch (e) {
			return false;
		}
	};

	// produce hid
	var produceID = function(){
		return +new Date + Math.round(Math.random()*1000000);
	};

	//______________________________________________________________________ History Shim

	var skipHashChange,
		skipInitPop = false,
		fromGo = false,
		agent = userAgent();

	window.history.parseURL = parseURL;
	window.history.config = {
		hashChangeAlone: true,
		setStateEvent:true,
		//setHashEvent:true,
		storageKey: "min-history",
		basePath: "/",
		hid: true
	};

	// some version of safari (and co) have no "state" property in history. force it.
	window.history.forceState = !("state" in window.history);
	if (window.history.forceState)
		window.history.state = null;

	// safari fire pop state on page load. skip it for consistency.
	if (agent.safari && location.relative !== window.history.config.basePath)
		skipInitPop = true;

	// produce href with hid in search part
	var getHrefWithHid = function(id, location) {
		var search = "?"+ (location.search || "") + "&hid=" + id;
		return (location.pathname || '/')
			+ search
			+ (location.hash ? ("#" + location.hash) : "");
	};

	// remove hid from location parts. store hid in location.
	var cleanHidFromLocation = function(location) {
		if (!location.search)
			return;
		location.search = location.search.replace(/&hid=([^&]+)/gi, function(c, v) {
			location.hid = v;
			return "";
		});
		location.path = location.pathname + (location.search ? ("?" + location.search) : "");
		location.relative = location.path + (location.hash ? ("#" + location.hash) : "");
		location.href = location.protocol + "://" + location.host + location.relative;
	};

	var removeBasePath = function(location){
		if(!window.history.config.basePath)
			return location;
		location.pathname = location.pathname.substring(window.history.config.basePath.length);
		location.path = location.pathname + (location.search ? ("?" + location.search) : "");
		location.relative = location.path + (location.hash ? ("#" + location.hash) : "");
		location.href = location.protocol + "://" + location.host + location.relative;
		return location;
	};

	var fireStateEvent = function(type, state, action) {
		// TODO : manage IE8 document.createEventObject
		var evt = document.createEvent("CustomEvent");
		evt.initEvent(type, true, true);
		evt.state = state || null;
		if(action)
			evt.action = action;
		return window.dispatchEvent(evt);
	};

	window.history.store = {
		states: null,
		index: -1,
		init: function() {
			if (!(window.history.sessionStorageSupported = sessionStorageSupported()))
				console.warn("You are using custom history API and Session Storage is not supported. You'll loose history when leaving page.");

			if (window.history.sessionStorageSupported)
				this.states = JSON.parse(sessionStorage.getItem(window.history.config.storageKey)) || [];
			else
				this.states = [];
			if (!Array.isArray(this.states))
				this.states = [];
		},
		current:function(){
			return this.states[this.index];
		},
		push: function(data, title, url) {
			var state = { data: data, title: title, url: url };
			if(window.history.config.hid)
			{
				state.data = state.data || {};
				state.data.hid = produceID();
			}
			this.index++;
			if (this.index < this.states.length)
				this.states = this.states.slice(0, this.index);
			this.states.push(state);
			return state;
		},
		replace: function(data, title, url) {
			var old = this.states[this.index],
				state = { data: data, title: title, url: url };
			if(window.history.config.hid)
			{
				state.data = state.data || {};
				state.data.hid = (old && old.data && old.data.hid) ? old.data.hid : produceID();
			}
			return this.states[this.index] = state;
		},
		go: function(increment) {
			var i = this.index + increment,
				state = this.states[i];
			if (!state)
				return null;
			this.index = i;
			return state;
		},
		find: function(id) {
			for (var i = 0, len = this.states.length, state; i < len && (state = this.states[i]); ++i)
				if (state.data && state.data.hid == id)
					return state;
			return null;
		},
		save: function() {
			if (window.history.sessionStorageSupported)
				sessionStorage.setItem(window.history.config.storageKey, JSON.stringify(this.states));
		}
	};
	var setState;
	if (!window.history.pushState) // EMULATED
	{
		window.history.emulated = true;
		setState = function(type, data, title, url)
		{
			if(title)
				document.title = title;
			this.location = parseURL(url);
			var href = this.location.relative;
			if (this.config.hid)
			{
				this.state = data = this.store[type](data, title, href).data;	// 'push' or 'replace' in store
				href = getHrefWithHid(data.hid, this.location); // launch hashchange
			}
			else
				this.state = data || null;
			if(this.config.setStateEvent)
				fireStateEvent("setstate", this.state, type);
			skipHashChange = true;
			window.location.hash = "!" + href; // launch hashchange
		}
		var originalGo = window.history.go;
		window.history.go = function(arg) {
			if(this.config.hid)
			{
				var state = this.store.go(arg);
				if (!state)
					return null;
				this.state = state.data;
			}
			else
				this.state = null;
			fromGo = true;
			originalGo.call(this, arg);
		};
		window.history.back = function(arg) {
			this.go(-1);
		};
		window.history.forward = function(arg) {
			this.go(1);
		};
	}
	else // NATIVE
	{
		var originalPushState = window.history.pushState,
			originalReplaceState = window.history.replaceState;
		setState = function(type, data, title, url){
			var original = originalReplaceState;
			if(type == 'push')
				original = originalPushState;
			if(title)
				document.title = title;
			this.location = parseURL(url);
			if(this.config.hid)
				data = this.store[type](data, title, this.location.relative).data; // push or replace in store
			if(this.forceState)
				this.state = data || null;

			var r = original.call(this, data, title, window.history.config.basePath + this.location.relative);
			if(this.config.setStateEvent)
				fireStateEvent('setstate', this.state, type);
			return r;
		}
	}

	window.history.pushState = function(data, title, url) {
		return setState.call(this, 'push', data, title, url);
	};
	window.history.replaceState = function(data, title, url) {
		return setState.call(this, 'replace', data, title, url);
	};

	// Native Only
	var popState = function(e) {
		// console.log("history popState : ", e);
		var stateData = e.state;
		if (window.history.forceState)
			window.history.state = stateData || null;

		if (skipInitPop) {	// safari popstate at init skipped.
			skipInitPop = false;
			e.stopImmediatePropagation();
			return false;
		}
		skipHashChange = false;
		var tmp = window.history.location,	// old
			current = window.history.location = removeBasePath(parseURL(location.href)), // new
			state;

		if(window.history.config.hid && stateData && stateData.hid)	// try to get state from hid
			state = window.history.store.find(stateData.hid);

		if(state)
		{
			// check url equality.
			if(current.relative !== state.url)
				throw new Error("bad state from native popstate (url dont match) "+current.relative+" - "+state.url);
			if(state.title) // force title
				document.title = state.title;
		}
		if (!tmp || tmp.path !== current.path) // relative has change : apply pop state
		{
			if (tmp && tmp.hash !== current.hash)
				skipHashChange = true;
			return true;
		}
		else // path doesn't change. check hash
		if (tmp.hash !== current.hash && window.history.config.hashChangeAlone)
		{
			e.stopImmediatePropagation();
			return false; // skip popstate if config.hashChangeAlone
		}
		return true; // nothing change : refresh
	};

	var hashChange = function(e) {
		if (skipHashChange) // skip after pushState/replaceState (emulated)  OR  after "path change" from popstate (native),
		{
			skipHashChange = false;
			e.stopImmediatePropagation();
			return false;
		}
		//______________________________________________________ POP STATE EMULATION
		if (window.history.emulated) // do popstate behaviour
		{
			var state,
				data = null,
				tmp = window.history.location, // swap currentLocation
				current = window.history.location = parseURL(location.hash.substring(2));
			if (window.history.config.hid) // could be from back/next button or manual hash change in address bar.
			{
				cleanHidFromLocation(current); // try to extract hid
				if(!fromGo)
				{
					if (current.hid)
						state = window.history.store.find(current.hid);
					if(state)
						data = state.data || null;
					window.history.state = data;
				}
			}
			if(state && state.title)	// force title
				document.title = state.title;

			if (!tmp || tmp.path !== current.path) { // path == pathname + ? + search
				// path has change : dispatch popstate and skip hashchange
				e.stopImmediatePropagation();
				fireStateEvent("popstate", data);
				return false;
			}
			// path doesn't change.
			// check hash change : if !hashChangeAlone : dispatch popstate
			if (tmp.hash !== current.hash && !window.history.config.hashChangeAlone)
				fireStateEvent("popstate", data);
		}
		return true; // propagate hashchange
	};

	window.history.init = function(opt) {
		for (var i in opt)
			this.config[i] = opt[i];
		window.addEventListener("hashchange", hashChange);
		if (!this.emulated)
		{
			window.addEventListener("popstate", popState);
			this.location = removeBasePath(parseURL(window.location.href));
		}
		else
			this.location = parseURL(window.location.href);
		if(this.config.hid)
		{
			this.store.init();
			window.addEventListener("unload", function() {
				window.history.store.save();
			});
		}
	};
})();<commit_msg>ie8 friendly<commit_after>/**
 * @author Gilles Coomans <gilles.coomans@gmail.com>
 * @licence MIT
 * Minimalistic, workable, understandable html5 history shim.
 * IE8+, FF3+, Safari, Chrome, ...
 *
 *
 * Remarque : for IE8, please use it in conjonction with https://github.com/WebReflection/ie8
 * (Events listener polyfill for IE8)
 *
 * TODO :
 * 	test in IE8
 * 	work on creatEvent polyfill
 *
 * 	write test
 *
 * manage basepath
 *
 * manage title
 *   ==> as all browser ignore it :
 *   	=> need to manage it through store even for native.
 *   	=> should use hid in data object to retreieve it on popstate
 *
 * manage internal/external flag
 */
(function() {
	'use strict';
	if (!window.history) // too old !!! ;)
		return;

	// ___________________________________________________________ UTILS
	var _uaMatch = function(ua) {
		ua = ua.toLowerCase();
		var match = /(chrome)[ \/]([\w.]+)/.exec(ua) ||
			/(webkit)[ \/]([\w.]+)/.exec(ua) ||
			/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) ||
			/(msie) ([\w.]+)/.exec(ua) ||
			ua.indexOf('compatible') < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || [];
		return {
			browser: match[1] || '',
			version: match[2] || '0'
		};
	};

	var userAgent = function() {
		var browser = {},
			matched = _uaMatch(navigator.userAgent);
		if (matched.browser) {
			browser[matched.browser] = true;
			browser.version = matched.version;
		}
		if (browser.chrome)
			browser.webkit = true;
		else if (browser.webkit)
			browser.safari = true;
		return browser;
	};

	var parseUri = function(str) {
		var o = parseUri.options,
			m = o.parser[o.strictMode ? "strict" : "loose"].exec(str),
			uri = {},
			i = 14;
		while (i--)
			uri[o.key[i]] = m[i] || "";
		uri[o.q.name] = {};
		uri[o.key[12]].replace(o.q.parser, function($0, $1, $2) {
			if ($1) uri[o.q.name][$1] = $2;
		});
		return uri;
	};

	parseUri.options = {
		strictMode: true,
		key: ["href", "protocol", "host", "userInfo", "user", "password", "hostname", "port", "relative", "pathname", "directory", "file", "search", "hash"],
		q: {
			name: "query",
			parser: /(?:^|&)([^&=]*)=?([^&]*)/g
		},
		parser: {
			strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
			loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
		}
	};

	var parseURL = function(url) {
		var obj = parseUri(url);
		obj.path = obj.pathname + (obj.search?"?":"") + obj.search;
		return obj;
	};

	// http://stackoverflow.com/questions/9077101/iphone-localstorage-quota-exceeded-err-issue/12976988#12976988
	var sessionStorageSupported = function() {
		try {
			sessionStorage.setItem("test", "test");
			sessionStorage.removeItem("test");
			return true;
		} catch (e) {
			return false;
		}
	};

	// produce hid
	var produceID = function(){
		return +new Date + Math.round(Math.random()*1000000);
	};

	//______________________________________________________________________ History Shim

	var skipHashChange,
		skipInitPop = false,
		fromGo = false,
		agent = userAgent();

	window.history.parseURL = parseURL;
	window.history.config = {
		hashChangeAlone: true,
		setStateEvent:true,
		//setHashEvent:true,
		storageKey: "min-history",
		basePath: "/",
		hid: true
	};

	// some version of safari (and co) have no "state" property in history. force it.
	window.history.forceState = !("state" in window.history);
	if (window.history.forceState)
		window.history.state = null;

	// safari fire pop state on page load. skip it for consistency.
	if (agent.safari && location.relative !== window.history.config.basePath)
		skipInitPop = true;

	// produce href with hid in search part
	var getHrefWithHid = function(id, location) {
		var search = "?"+ (location.search || "") + "&hid=" + id;
		return (location.pathname || '/')
			+ search
			+ (location.hash ? ("#" + location.hash) : "");
	};

	// remove hid from location parts. store hid in location.
	var cleanHidFromLocation = function(location) {
		if (!location.search)
			return;
		location.search = location.search.replace(/&hid=([^&]+)/gi, function(c, v) {
			location.hid = v;
			return "";
		});
		location.path = location.pathname + (location.search ? ("?" + location.search) : "");
		location.relative = location.path + (location.hash ? ("#" + location.hash) : "");
		location.href = location.protocol + "://" + location.host + location.relative;
	};

	var removeBasePath = function(location){
		if(!window.history.config.basePath)
			return location;
		location.pathname = location.pathname.substring(window.history.config.basePath.length);
		location.path = location.pathname + (location.search ? ("?" + location.search) : "");
		location.relative = location.path + (location.hash ? ("#" + location.hash) : "");
		location.href = location.protocol + "://" + location.host + location.relative;
		return location;
	};

	var fireStateEvent = function(type, state, action) {
		var evt = document.createEvent("Event");
		evt.initEvent(type, true, true);
		evt.state = state || null;
		if(action)
			evt.action = action;
		return window.dispatchEvent(evt);
	};

	window.history.store = {
		states: null,
		index: -1,
		init: function() {
			if (!(window.history.sessionStorageSupported = sessionStorageSupported()))
				console.warn("You are using custom history API and Session Storage is not supported. You'll loose history when leaving page.");

			if (window.history.sessionStorageSupported)
				this.states = JSON.parse(sessionStorage.getItem(window.history.config.storageKey)) || [];
			else
				this.states = [];
			if (!this.states.forEach)
				this.states = [];
		},
		current:function(){
			return this.states[this.index];
		},
		push: function(data, title, url) {
			var state = { data: data, title: title, url: url };
			if(window.history.config.hid)
			{
				state.data = state.data || {};
				state.data.hid = produceID();
			}
			this.index++;
			if (this.index < this.states.length)
				this.states = this.states.slice(0, this.index);
			this.states.push(state);
			return state;
		},
		replace: function(data, title, url) {
			var old = this.states[this.index],
				state = { data: data, title: title, url: url };
			if(window.history.config.hid)
			{
				state.data = state.data || {};
				state.data.hid = (old && old.data && old.data.hid) ? old.data.hid : produceID();
			}
			return this.states[this.index] = state;
		},
		go: function(increment) {
			var i = this.index + increment,
				state = this.states[i];
			if (!state)
				return null;
			this.index = i;
			return state;
		},
		find: function(id) {
			for (var i = 0, len = this.states.length, state; i < len && (state = this.states[i]); ++i)
				if (state.data && state.data.hid == id)
					return state;
			return null;
		},
		save: function() {
			if (window.history.sessionStorageSupported)
				sessionStorage.setItem(window.history.config.storageKey, JSON.stringify(this.states));
		}
	};
	var setState;
	if (!window.history.pushState) // EMULATED
	{
		window.history.emulated = true;
		setState = function(type, data, title, url)
		{
			if(title)
				document.title = title;
			this.location = parseURL(url);
			var href = this.location.relative;
			if (this.config.hid)
			{
				this.state = data = this.store[type](data, title, href).data;	// 'push' or 'replace' in store
				href = getHrefWithHid(data.hid, this.location); // launch hashchange
			}
			else
				this.state = data || null;
			if(this.config.setStateEvent)
				fireStateEvent("setstate", this.state, type);
			skipHashChange = true;
			window.location.hash = "!" + href; // launch hashchange
		}
		var originalGo = window.history.go;
		window.history.go = function(arg) {
			if(this.config.hid)
			{
				var state = this.store.go(arg);
				if (!state)
					return null;
				this.state = state.data;
			}
			else
				this.state = null;
			fromGo = true;
			originalGo.call(this, arg);
		};
		window.history.back = function(arg) {
			this.go(-1);
		};
		window.history.forward = function(arg) {
			this.go(1);
		};
	}
	else // NATIVE
	{
		var originalPushState = window.history.pushState,
			originalReplaceState = window.history.replaceState;
		setState = function(type, data, title, url){
			var original = originalReplaceState;
			if(type == 'push')
				original = originalPushState;
			if(title)
				document.title = title;
			this.location = parseURL(url);
			if(this.config.hid)
				data = this.store[type](data, title, this.location.relative).data; // push or replace in store
			if(this.forceState)
				this.state = data || null;

			var r = original.call(this, data, title, window.history.config.basePath + this.location.relative);
			if(this.config.setStateEvent)
				fireStateEvent('setstate', this.state, type);
			return r;
		}
	}

	window.history.pushState = function(data, title, url) {
		return setState.call(this, 'push', data, title, url);
	};
	window.history.replaceState = function(data, title, url) {
		return setState.call(this, 'replace', data, title, url);
	};

	// Native Only
	var popState = function(e) {
		// console.log("history popState : ", e);
		var stateData = e.state;
		if (window.history.forceState)
			window.history.state = stateData || null;

		if (skipInitPop) {	// safari popstate at init skipped.
			skipInitPop = false;
			e.stopImmediatePropagation();
			return false;
		}
		skipHashChange = false;
		var tmp = window.history.location,	// old
			current = window.history.location = removeBasePath(parseURL(location.href)), // new
			state;

		if(window.history.config.hid && stateData && stateData.hid)	// try to get state from hid
			state = window.history.store.find(stateData.hid);

		if(state)
		{
			// check url equality.
			if(current.relative !== state.url)
				throw new Error("bad state from native popstate (url dont match) "+current.relative+" - "+state.url);
			if(state.title) // force title
				document.title = state.title;
		}
		if (!tmp || tmp.path !== current.path) // relative has change : apply pop state
		{
			if (tmp && tmp.hash !== current.hash)
				skipHashChange = true;
			return true;
		}
		else // path doesn't change. check hash
		if (tmp.hash !== current.hash && window.history.config.hashChangeAlone)
		{
			e.stopImmediatePropagation();
			return false; // skip popstate if config.hashChangeAlone
		}
		return true; // nothing change : refresh
	};

	var hashChange = function(e) {
		if (skipHashChange) // skip after pushState/replaceState (emulated)  OR  after "path change" from popstate (native),
		{
			skipHashChange = false;
			e.stopImmediatePropagation();
			return false;
		}
		//______________________________________________________ POP STATE EMULATION
		if (window.history.emulated) // do popstate behaviour
		{
			var state,
				data = null,
				tmp = window.history.location, // swap currentLocation
				current = window.history.location = parseURL(location.hash.substring(2));
			if (window.history.config.hid) // could be from back/next button or manual hash change in address bar.
			{
				cleanHidFromLocation(current); // try to extract hid
				if(!fromGo)
				{
					if (current.hid)
						state = window.history.store.find(current.hid);
					if(state)
						data = state.data || null;
					window.history.state = data;
				}
			}
			if(state && state.title)	// force title
				document.title = state.title;

			if (!tmp || tmp.path !== current.path) { // path == pathname + ? + search
				// path has change : dispatch popstate and skip hashchange
				e.stopImmediatePropagation();
				fireStateEvent("popstate", data);
				return false;
			}
			// path doesn't change.
			// check hash change : if !hashChangeAlone : dispatch popstate
			if (tmp.hash !== current.hash && !window.history.config.hashChangeAlone)
				fireStateEvent("popstate", data);
		}
		return true; // propagate hashchange
	};

	window.history.init = function(opt) {
		for (var i in opt)
			this.config[i] = opt[i];
		window.addEventListener("hashchange", hashChange);
		if (!this.emulated)
		{
			window.addEventListener("popstate", popState);
			this.location = removeBasePath(parseURL(window.location.href));
		}
		else
			this.location = parseURL(window.location.href);
		if(this.config.hid)
		{
			this.store.init();
			window.addEventListener("unload", function() {
				window.history.store.save();
			});
		}
	};
})();<|endoftext|>


<commit_before>'use strict';

/**
 * GraphQL.js service
 *
 * @description: A set of functions similar to controller's actions to avoid code duplication.
 */

const policyUtils = require('strapi-utils').policy;

const fs = require('fs');
const path = require('path');
const _ = require('lodash');
const pluralize = require('pluralize');
const graphql = require('graphql');
const { makeExecutableSchema } = require('graphql-tools');
const GraphQLJSON = require('graphql-type-json');

module.exports = {

  /**
   * Receive an Object and return a string which is following the GraphQL specs.
   *
   * @return String
   */

  formatGQL: function (fields, description = {}, model = {}, type = 'field') {
    const typeFields = JSON.stringify(fields, null, 2).replace(/['",]+/g, '');
    const lines = typeFields.split('\n');

    // Try to add description for field.
    if (type === 'field') {
      return lines
        .map((line, index) => {
          if ([0, lines.length - 1].includes(index)) {
            return ``;
          }

          const split = line.split(':');
          const attribute = _.trim(split[0]);
          const info = description[attribute] || _.get(model, `attributes.${attribute}.description`);

          if (info) {
            return `  """\n    ${info}\n  """\n${line}`;
          }

          return line;
        })
        .join('\n');
    }

    return lines
        .map((line, index) => {
          if ([0, lines.length - 1].includes(index)) {
            return ``;
          }

          return line;
        })
        .join('\n');
  },

  /**
   * Retrieve description from variable and return a string which follow the GraphQL specs.
   *
   * @return String
   */

  getDescription: (description, model = {}) => {
    const format = `"""\n`;

    const str = _.get(description, `_description`) ||
      _.isString(description) ? description : undefined ||
      model.description;

    if (str) {
      return `${format}${str}\n${format}`;
    }

    return ``;
  },

  convertToParams: (params) => {
    return Object.keys(params).reduce((acc, current) => {
      return Object.assign(acc, {
        [`_${current}`]: params[current]
      });
    }, {});
  },

  /**
   * Convert Strapi type to GraphQL type.
   *
   * @return String
   */

  convertType: (attr = {}) => {
    // Type.
    if (attr.type) {
      switch (attr.type) {
        case 'string':
        case 'text':
          return 'String';
        case 'boolean':
          return 'Boolean';
        case 'integer':
          return 'Int';
        default:
          return 'String';
      }
    }

    const ref = attr.model || attr.collection;

    // Association.
    if (ref && ref !== '*') {
      // Add bracket or not.
      const plural = !_.isEmpty(attr.collection);
      const globalId = attr.plugin ?
        strapi.plugins[attr.plugin].models[ref].globalId:
        strapi.models[ref].globalId;

      if (plural) {
        return `[${globalId}]`;
      }

      return globalId;
    }
  },

  /**
   * Execute policies before the specified resolver.
   *
   * @return Promise or Error.
   */

  composeResolver: async function (obj, options, context, _schema, plugin, name, isSingular) {
    const params = {
      model: name
    };

    const queryOpts = plugin ? { source: plugin } : {};

    // Retrieve generic service from the Content Manager plugin.
    const resolvers = strapi.plugins['content-manager'].services['contentmanager'];

    // Extract custom resolver or _type description.
    const { resolver: handler = {} } = _schema;

    // Retrieve policies.
    const policies = isSingular ?
      _.get(handler, `Query.${pluralize.singular(name)}.policy`, []):
      _.get(handler, `Query.${pluralize.plural(name)}.policy`, []);

    // Retrieve resolver. It could be the custom resolver of the user
    // or the shadow CRUD resolver (aka Content-Manager).
    const resolver = (() => {
      if (isSingular) {
        return _.get(handler, `Query.${pluralize.singular(name)}.resolver`,
          async () => {
            const value = await resolvers.fetch({ ...params, id: options.id }, plugin, []);

            return value.toJSON ? value.toJSON() : value;
          }
        );
      }

      const resolver = _.get(handler, `Query.${pluralize.plural(name)}.resolver`,
        async () => {
          const convertedParams = strapi.utils.models.convertParams(name, this.convertToParams(options));
          const where = strapi.utils.models.convertParams(name, options.where || {});

          // Content-Manager specificity.
          convertedParams.skip = convertedParams.start;
          convertedParams.query = where.where;

          const value = await resolvers.fetchAll(params, { ...queryOpts, ...convertedParams, populate: [] });

          return value.toJSON ? value.toJSON() : value;
        }
      );

      return resolver;
    })();

    const policiesFn = [];

    // Populate policies.
    policies.forEach(policy => policyUtils.get(policy, plugin, policiesFn, 'GraphQL error'));

    // Execute policies stack.
    const policy = await strapi.koaMiddlewares.compose(policiesFn)(context);

    // Policy doesn't always return errors but they update the current context.
    if (_.isError(context.response.body) || _.get(context.response.body, 'isBoom')) {
      return context.response.body;
    }

    // When everything is okay, the policy variable should be undefined
    // so it will return the resolver instead.

    // Note: The resolver can be a function or promise.
    return policy || _.isFunction(resolver) ? resolver.call(null, obj, options, context) : resolver;
  },

  /**
   * Construct the GraphQL query & definition and apply the right resolvers.
   *
   * @return Object
   */

  shadowCRUD: function (models, plugin) {
    // Retrieve generic service from the Content Manager plugin.
    const resolvers = strapi.plugins['content-manager'].services['contentmanager'];

    const initialState = { definition: ``, query: {}, resolver: { Query : {} } };

    if (_.isEmpty(models)) {
      return initialState;
    }

    return models.reduce((acc, name) => {
      const model = plugin ?
        strapi.plugins[plugin].models[name]:
        strapi.models[name];

      // Setup initial state with default attribute that should be displayed
      // but these attributes are not properly defined in the models.
      const initialState = {
        [model.primaryKey]: 'String'
      };

      // Add timestamps attributes.
      if (_.get(model, 'options.timestamps') === true) {
        Object.assign(initialState, {
          created_at: 'String',
          updated_at: 'String'
        });
      }

      const globalId = model.globalId;
      const _schema = _.cloneDeep(_.get(strapi.plugins, `graphql.config._schema.graphql`, {}));

      // Retrieve user customisation.
      const { _type = {} } = _schema;

      // Convert our layer Model to the GraphQL DL.
      const attributes = Object.keys(model.attributes)
        .reduce((acc, attribute) => {
          // Convert our type to the GraphQL type.
          acc[attribute] = this.convertType(model.attributes[attribute]);

          return acc;
        }, initialState);

      // Add parameters to optimize association query.
      (model.associations || [])
        .filter(association => association.type === 'collection')
        .forEach(association => {
          attributes[`${association.alias}(sort: String, limit: Int, start: Int, where: JSON)`] = attributes[association.alias];

          delete attributes[association.alias];
        })

      acc.definition += `${this.getDescription(_type[globalId], model)}type ${globalId} {${this.formatGQL(attributes, _type[globalId], model)}}\n\n`;

      // Add definition to the schema but this type won't be "queriable".
      if (_type[model.globalId] === false || _.get(_type, `${model.globalId}.enabled`) === false) {
        return acc;
      }

      Object.assign(acc.query, {
        [`${pluralize.plural(name)}(sort: String, limit: Int, start: Int, where: JSON)`]: `[${model.globalId}]`,
        [`${pluralize.singular(name)}(id: String!)`]: model.globalId
      });

      // TODO:
      // - Handle mutations.
      _.merge(acc.resolver, {
        Query : {
          [pluralize.plural(name)]: (obj, options, context) => this.composeResolver(
            obj,
            options,
            context,
            _schema,
            plugin,
            name,
            false
          ),
          [pluralize.singular(name)]: (obj, options, context) => this.composeResolver(
            obj,
            options,
            context,
            _schema,
            plugin,
            name,
            true
          )
        }
      });

      // Build associations queries.
      (model.associations || []).forEach(association => {
        if (association.nature === 'manyMorphToMany') {
          return;
        }

        if (!acc.resolver[globalId]) {
          acc.resolver[globalId] = {};
        }

        // TODO:
        // - Handle limit, skip, etc options
        _.merge(acc.resolver[globalId], {
          [association.alias]: async (obj, options, context) => {
            // Construct parameters object to retrieve the correct related entries.
            const params = {
              model: association.model || association.collection,
            };

            const queryOpts = {
              source: association.plugin
            };

            if (association.type === 'model') {
              params.id = obj[association.alias];
            } else {
              // Get attribute.
              const attr = association.plugin ?
                strapi.plugins[association.plugin].models[association.collection].attributes[association.via]:
                strapi.models[association.collection].attributes[association.via];

              // Get refering model.
              const ref = attr.plugin ?
                strapi.plugins[attr.plugin].models[attr.model || attr.collection]:
                strapi.models[attr.model || attr.collection];

              // Apply optional arguments to make more precise nested request.
              const convertedParams = strapi.utils.models.convertParams(name, this.convertToParams(options));
              const where = strapi.utils.models.convertParams(name, options.where || {});

              // Limit, order, etc.
              Object.assign(queryOpts, convertedParams);

              // Skip.
              queryOpts.skip = convertedParams.start;

              // Where.
              queryOpts.query = strapi.utils.models.convertParams(name, {
                // Construct the "where" query to only retrieve entries which are
                // related to this entry.
                [association.via]: obj[ref.primaryKey],
                ...where.where
              }).where;
            }

            const value = await (association.model ?
              resolvers.fetch(params, association.plugin, []):
              resolvers.fetchAll(params, { ...queryOpts, populate: [] }));

            return value.toJSON ? value.toJSON() : value;
          }
        });
      });

      return acc;
    }, initialState);
  },

  /**
   * Generate GraphQL schema.
   *
   * @return Schema
   */

  generateSchema: function () {
    // Generate type definition and query/mutation for models.
    const shadowCRUD = strapi.plugins.graphql.config.shadowCRUD !== false ? (() => {
      // Exclude core models.
      const models = Object.keys(strapi.models).filter(model => model !== 'core_store');

      // Reproduce the same pattern for each plugin.
      return Object.keys(strapi.plugins).reduce((acc, plugin) => {
        const { definition, query, resolver } = this.shadowCRUD(Object.keys(strapi.plugins[plugin].models), plugin);

        // We cannot put this in the merge because it's a string.
        acc.definition += definition || ``;

        return _.merge(acc, {
          query,
          resolver
        });
      }, this.shadowCRUD(models));
    })() : {};

    // Extract custom definition, query or resolver.
    const { definition, query, resolver } = strapi.plugins.graphql.config._schema.graphql;

    // Build resolvers.
    const resolvers = _.omitBy(_.merge(shadowCRUD.resolver, resolver), _.isEmpty) || {};

    // Transform object to only contain function.
    Object.keys(resolvers).reduce((acc, type) => {
      return Object.keys(acc[type]).reduce((acc, resolver) => {
        // Disabled this query.
        if (acc[type][resolver] === false) {
          delete acc[type][resolver];

          return acc;
        }

        acc[type][resolver] = _.isFunction(acc[type][resolver]) ?
          acc[type][resolver]:
          acc[type][resolver].resolver;

        return acc;
      }, acc);
    }, resolvers);

    // Return empty schema when there is no model.
    if (_.isEmpty(shadowCRUD.definition) && _.isEmpty(definition)) {
      return {};
    }

    // Concatenate.
    const typeDefs =
      definition +
      shadowCRUD.definition +
      `type Query {${this.formatGQL(shadowCRUD.query, {}, null, 'query')}${query}}\n` +
      this.addCustomScalar(resolvers);

      // console.log(typeDefs);

    // Build schema.
    const schema = makeExecutableSchema({
      typeDefs,
      resolvers,
    });

    // Write schema.
    this.writeGenerateSchema(graphql.printSchema(schema));

    // Temporary variable to store the entire GraphQL configuration.
    delete strapi.plugins.graphql.config._schema.graphql;

    return schema;
  },

  /**
   * Add custom scalar type such as JSON.
   *
   * @return void
   */

  addCustomScalar: (resolvers) => {
    Object.assign(resolvers, {
      JSON: GraphQLJSON
    });

    return `scalar JSON`;
  },

  /**
   * Save into a file the readable GraphQL schema.
   *
   * @return void
   */

  writeGenerateSchema(schema) {
    // Disable auto-reload.
    strapi.reload.isWatching = false;

    const generatedFolder = path.resolve(strapi.config.appPath, 'plugins', 'graphql', 'config', 'generated');

    // Create folder if necessary.
    try {
      fs.accessSync(generatedFolder, fs.constants.R_OK | fs.constants.W_OK);
    } catch (err) {
      if (err && err.code === 'ENOENT') {
        fs.mkdirSync(generatedFolder);
      } else {
        console.error(err);
      }
    }

    fs.writeFileSync(path.join(generatedFolder, 'schema.graphql'), schema);

    strapi.reload.isWatching = true;
  }

};
<commit_msg>Be able to add description and deprecated reason on query<commit_after>'use strict';

/**
 * GraphQL.js service
 *
 * @description: A set of functions similar to controller's actions to avoid code duplication.
 */

const policyUtils = require('strapi-utils').policy;

const fs = require('fs');
const path = require('path');
const _ = require('lodash');
const pluralize = require('pluralize');
const graphql = require('graphql');
const { makeExecutableSchema } = require('graphql-tools');
const GraphQLJSON = require('graphql-type-json');

module.exports = {

  /**
   * Receive an Object and return a string which is following the GraphQL specs.
   *
   * @return String
   */

  formatGQL: function (fields, description = {}, model = {}, type = 'field') {
    const typeFields = JSON.stringify(fields, null, 2).replace(/['",]+/g, '');
    const lines = typeFields.split('\n');

    // Try to add description for field.
    if (type === 'field') {
      return lines
        .map((line, index) => {
          if ([0, lines.length - 1].includes(index)) {
            return ``;
          }

          const split = line.split(':');
          const attribute = _.trim(split[0]);
          const info = (_.isString(description[attribute]) ? description[attribute] : _.get(description[attribute], 'description')) || _.get(model, `attributes.${attribute}.description`);
          const deprecated = _.get(description[attribute], 'deprecated') || _.get(model, `attributes.${attribute}.deprecated`);

          if (info) {
            line = `  """\n    ${info}\n  """\n${line}`;
          }

          if (deprecated) {
            line = `${line} @deprecated(reason: "${deprecated}")`;
          }

          return line;
        })
        .join('\n');
    } else if (type === 'query') {
      return lines
        .map((line, index) => {
          if ([0, lines.length - 1].includes(index)) {
            return ``;
          }

          const split = Object.keys(fields)[index - 1].split('(');
          const attribute = _.trim(split[0]);
          const info = _.get(description[attribute], 'description');
          const deprecated = _.get(description[attribute], 'deprecated');

          if (info) {
            line = `  """\n    ${info}\n  """\n${line}`;
          }

          if (deprecated) {
            line = `${line} @deprecated(reason: "${deprecated}")`;
          }

          return line;
        })
        .join('\n');
    }

    return lines
        .map((line, index) => {
          if ([0, lines.length - 1].includes(index)) {
            return ``;
          }

          return line;
        })
        .join('\n');
  },

  /**
   * Retrieve description from variable and return a string which follow the GraphQL specs.
   *
   * @return String
   */

  getDescription: (description, model = {}) => {
    const format = `"""\n`;

    const str = _.get(description, `_description`) ||
      _.isString(description) ? description : undefined ||
      model.description;

    if (str) {
      return `${format}${str}\n${format}`;
    }

    return ``;
  },

  convertToParams: (params) => {
    return Object.keys(params).reduce((acc, current) => {
      return Object.assign(acc, {
        [`_${current}`]: params[current]
      });
    }, {});
  },

  /**
   * Convert Strapi type to GraphQL type.
   *
   * @return String
   */

  convertType: (attr = {}) => {
    // Type.
    if (attr.type) {
      switch (attr.type) {
        case 'string':
        case 'text':
          return 'String';
        case 'boolean':
          return 'Boolean';
        case 'integer':
          return 'Int';
        default:
          return 'String';
      }
    }

    const ref = attr.model || attr.collection;

    // Association.
    if (ref && ref !== '*') {
      // Add bracket or not.
      const plural = !_.isEmpty(attr.collection);
      const globalId = attr.plugin ?
        strapi.plugins[attr.plugin].models[ref].globalId:
        strapi.models[ref].globalId;

      if (plural) {
        return `[${globalId}]`;
      }

      return globalId;
    }
  },

  /**
   * Execute policies before the specified resolver.
   *
   * @return Promise or Error.
   */

  composeResolver: async function (obj, options, context, _schema, plugin, name, isSingular) {
    const params = {
      model: name
    };

    const queryOpts = plugin ? { source: plugin } : {};

    // Retrieve generic service from the Content Manager plugin.
    const resolvers = strapi.plugins['content-manager'].services['contentmanager'];

    // Extract custom resolver or _type description.
    const { resolver: handler = {} } = _schema;

    // Retrieve policies.
    const policies = isSingular ?
      _.get(handler, `Query.${pluralize.singular(name)}.policy`, []):
      _.get(handler, `Query.${pluralize.plural(name)}.policy`, []);

    // Retrieve resolver. It could be the custom resolver of the user
    // or the shadow CRUD resolver (aka Content-Manager).
    const resolver = (() => {
      if (isSingular) {
        return _.get(handler, `Query.${pluralize.singular(name)}.resolver`,
          async () => {
            const value = await resolvers.fetch({ ...params, id: options.id }, plugin, []);

            return value.toJSON ? value.toJSON() : value;
          }
        );
      }

      const resolver = _.get(handler, `Query.${pluralize.plural(name)}.resolver`,
        async () => {
          const convertedParams = strapi.utils.models.convertParams(name, this.convertToParams(options));
          const where = strapi.utils.models.convertParams(name, options.where || {});

          // Content-Manager specificity.
          convertedParams.skip = convertedParams.start;
          convertedParams.query = where.where;

          const value = await resolvers.fetchAll(params, { ...queryOpts, ...convertedParams, populate: [] });

          return value.toJSON ? value.toJSON() : value;
        }
      );

      return resolver;
    })();

    const policiesFn = [];

    // Populate policies.
    policies.forEach(policy => policyUtils.get(policy, plugin, policiesFn, 'GraphQL error'));

    // Execute policies stack.
    const policy = await strapi.koaMiddlewares.compose(policiesFn)(context);

    // Policy doesn't always return errors but they update the current context.
    if (_.isError(context.response.body) || _.get(context.response.body, 'isBoom')) {
      return context.response.body;
    }

    // When everything is okay, the policy variable should be undefined
    // so it will return the resolver instead.

    // Note: The resolver can be a function or promise.
    return policy || _.isFunction(resolver) ? resolver.call(null, obj, options, context) : resolver;
  },

  /**
   * Construct the GraphQL query & definition and apply the right resolvers.
   *
   * @return Object
   */

  shadowCRUD: function (models, plugin) {
    // Retrieve generic service from the Content Manager plugin.
    const resolvers = strapi.plugins['content-manager'].services['contentmanager'];

    const initialState = { definition: ``, query: {}, resolver: { Query : {} } };

    if (_.isEmpty(models)) {
      return initialState;
    }

    return models.reduce((acc, name) => {
      const model = plugin ?
        strapi.plugins[plugin].models[name]:
        strapi.models[name];

      // Setup initial state with default attribute that should be displayed
      // but these attributes are not properly defined in the models.
      const initialState = {
        [model.primaryKey]: 'String'
      };

      // Add timestamps attributes.
      if (_.get(model, 'options.timestamps') === true) {
        Object.assign(initialState, {
          created_at: 'String',
          updated_at: 'String'
        });
      }

      const globalId = model.globalId;
      const _schema = _.cloneDeep(_.get(strapi.plugins, `graphql.config._schema.graphql`, {}));

      // Retrieve user customisation.
      const { _type = {} } = _schema;

      // Convert our layer Model to the GraphQL DL.
      const attributes = Object.keys(model.attributes)
        .reduce((acc, attribute) => {
          // Convert our type to the GraphQL type.
          acc[attribute] = this.convertType(model.attributes[attribute]);

          return acc;
        }, initialState);

      // Add parameters to optimize association query.
      (model.associations || [])
        .filter(association => association.type === 'collection')
        .forEach(association => {
          attributes[`${association.alias}(sort: String, limit: Int, start: Int, where: JSON)`] = attributes[association.alias];

          delete attributes[association.alias];
        })

      acc.definition += `${this.getDescription(_type[globalId], model)}type ${globalId} {${this.formatGQL(attributes, _type[globalId], model)}}\n\n`;

      // Add definition to the schema but this type won't be "queriable".
      if (_type[model.globalId] === false || _.get(_type, `${model.globalId}.enabled`) === false) {
        return acc;
      }

      Object.assign(acc.query, {
        [`${pluralize.plural(name)}(sort: String, limit: Int, start: Int, where: JSON)`]: `[${model.globalId}]`,
        [`${pluralize.singular(name)}(id: String!)`]: model.globalId
      });

      // TODO:
      // - Handle mutations.
      _.merge(acc.resolver, {
        Query : {
          [pluralize.plural(name)]: (obj, options, context) => this.composeResolver(
            obj,
            options,
            context,
            _schema,
            plugin,
            name,
            false
          ),
          [pluralize.singular(name)]: (obj, options, context) => this.composeResolver(
            obj,
            options,
            context,
            _schema,
            plugin,
            name,
            true
          )
        }
      });

      // Build associations queries.
      (model.associations || []).forEach(association => {
        if (association.nature === 'manyMorphToMany') {
          return;
        }

        if (!acc.resolver[globalId]) {
          acc.resolver[globalId] = {};
        }

        // TODO:
        // - Handle limit, skip, etc options
        _.merge(acc.resolver[globalId], {
          [association.alias]: async (obj, options, context) => {
            // Construct parameters object to retrieve the correct related entries.
            const params = {
              model: association.model || association.collection,
            };

            const queryOpts = {
              source: association.plugin
            };

            if (association.type === 'model') {
              params.id = obj[association.alias];
            } else {
              // Get attribute.
              const attr = association.plugin ?
                strapi.plugins[association.plugin].models[association.collection].attributes[association.via]:
                strapi.models[association.collection].attributes[association.via];

              // Get refering model.
              const ref = attr.plugin ?
                strapi.plugins[attr.plugin].models[attr.model || attr.collection]:
                strapi.models[attr.model || attr.collection];

              // Apply optional arguments to make more precise nested request.
              const convertedParams = strapi.utils.models.convertParams(name, this.convertToParams(options));
              const where = strapi.utils.models.convertParams(name, options.where || {});

              // Limit, order, etc.
              Object.assign(queryOpts, convertedParams);

              // Skip.
              queryOpts.skip = convertedParams.start;

              // Where.
              queryOpts.query = strapi.utils.models.convertParams(name, {
                // Construct the "where" query to only retrieve entries which are
                // related to this entry.
                [association.via]: obj[ref.primaryKey],
                ...where.where
              }).where;
            }

            const value = await (association.model ?
              resolvers.fetch(params, association.plugin, []):
              resolvers.fetchAll(params, { ...queryOpts, populate: [] }));

            return value.toJSON ? value.toJSON() : value;
          }
        });
      });

      return acc;
    }, initialState);
  },

  /**
   * Generate GraphQL schema.
   *
   * @return Schema
   */

  generateSchema: function () {
    // Generate type definition and query/mutation for models.
    const shadowCRUD = strapi.plugins.graphql.config.shadowCRUD !== false ? (() => {
      // Exclude core models.
      const models = Object.keys(strapi.models).filter(model => model !== 'core_store');

      // Reproduce the same pattern for each plugin.
      return Object.keys(strapi.plugins).reduce((acc, plugin) => {
        const { definition, query, resolver } = this.shadowCRUD(Object.keys(strapi.plugins[plugin].models), plugin);

        // We cannot put this in the merge because it's a string.
        acc.definition += definition || ``;

        return _.merge(acc, {
          query,
          resolver
        });
      }, this.shadowCRUD(models));
    })() : {};

    // Extract custom definition, query or resolver.
    const { definition, query, resolver = {} } = strapi.plugins.graphql.config._schema.graphql;

    // Build resolvers.
    const resolvers = _.omitBy(_.merge(shadowCRUD.resolver, resolver), _.isEmpty) || {};

    // Transform object to only contain function.
    Object.keys(resolvers).reduce((acc, type) => {
      return Object.keys(acc[type]).reduce((acc, resolver) => {
        // Disabled this query.
        if (acc[type][resolver] === false) {
          delete acc[type][resolver];

          return acc;
        }

        acc[type][resolver] = _.isFunction(acc[type][resolver]) ?
          acc[type][resolver]:
          acc[type][resolver].resolver;

        return acc;
      }, acc);
    }, resolvers);

    // Return empty schema when there is no model.
    if (_.isEmpty(shadowCRUD.definition) && _.isEmpty(definition)) {
      return {};
    }

    // Concatenate.
    const typeDefs =
      definition +
      shadowCRUD.definition +
      `type Query {${this.formatGQL(shadowCRUD.query, resolver.Query, null, 'query')}${query}}\n` +
      this.addCustomScalar(resolvers);

    // console.log(typeDefs);

    // Build schema.
    const schema = makeExecutableSchema({
      typeDefs,
      resolvers,
    });

    // Write schema.
    this.writeGenerateSchema(graphql.printSchema(schema));

    // Temporary variable to store the entire GraphQL configuration.
    delete strapi.plugins.graphql.config._schema.graphql;

    return schema;
  },

  /**
   * Add custom scalar type such as JSON.
   *
   * @return void
   */

  addCustomScalar: (resolvers) => {
    Object.assign(resolvers, {
      JSON: GraphQLJSON
    });

    return `scalar JSON`;
  },

  /**
   * Save into a file the readable GraphQL schema.
   *
   * @return void
   */

  writeGenerateSchema(schema) {
    // Disable auto-reload.
    strapi.reload.isWatching = false;

    const generatedFolder = path.resolve(strapi.config.appPath, 'plugins', 'graphql', 'config', 'generated');

    // Create folder if necessary.
    try {
      fs.accessSync(generatedFolder, fs.constants.R_OK | fs.constants.W_OK);
    } catch (err) {
      if (err && err.code === 'ENOENT') {
        fs.mkdirSync(generatedFolder);
      } else {
        console.error(err);
      }
    }

    fs.writeFileSync(path.join(generatedFolder, 'schema.graphql'), schema);

    strapi.reload.isWatching = true;
  }

};
<|endoftext|>


<commit_before>import React, { PropTypes } from 'react';
import { Header, Wrapper } from 'bytes';

const Default = ({ children }) => (

    <Wrapper className="u-pv">
      {children}
    </Wrapper>

);

Default.propTypes = {
  children: PropTypes.node.isRequired,
};

export default Default;
<commit_msg>fix: default layout in production build<commit_after>import React, { PropTypes } from 'react';
/**
 * NOTE: For some reason importing this bytes using:

     import { Wrapper, Header } from 'bytes'

 * doesn't work in production build
 */
import Header from 'bytes/Header';
import Wrapper from 'bytes/Wrapper';

const Default = ({ children }) => (

    <Wrapper className="u-pv">
      {children}
    </Wrapper>

);

Default.propTypes = {
  children: PropTypes.node.isRequired,
};

export default Default;
<|endoftext|>