[
  {
    "path": ".editorconfig",
    "content": "# http://editorconfig.org\nroot = true\n\n[*]\nindent_style = space\nindent_size = 4\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[*.md]\ntrim_trailing_whitespace = false"
  },
  {
    "path": ".gitignore",
    "content": "# Logs\nlogs\n*.log\n\n# Runtime data\npids\n*.pid\n*.seed\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Compiled binary addons (http://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directory\n# Commenting this out is preferred by some people, see\n# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-\nnode_modules\n\n# Users Environment Variables\n.lock-wscript\n\n# DevOps\n*.box\n.vagrant\n\n# IDEs\n.idea/\n\n.envrc\n\n.bash_history\n.cache\n.config\n.ionic\n.node-gyp\n.npm\n\nuploads/\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015 Martin Micunda\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n"
  },
  {
    "path": "README.md",
    "content": "Ionic Photo Gallery\n======================\n \n[![Deploy](https://www.herokucdn.com/deploy/button.png)](https://heroku.com/deploy?template=https://github.com/martinmicunda/ionic-photo-gallery/tree/heroku)\n\nA hybrid app with authentication that allows registered users view a gallery of photos they have uploaded via the camera phone. The blog post I have written about this project can be found on my [blog](http://martinmicunda.com/2015/04/10/build-ionic-photo-gallery-app-I/). \n\n![Ionic Photo Gallery Screenshots I](ionic-photo-gallery-screenshots-I.jpg \"Ionic Photo Gallery Screenshots I\")\n![Ionic Photo Gallery Screenshots II](ionic-photo-gallery-screenshots-II.jpg \"Ionic Photo Gallery Screenshots II\")\n\n## Table of Contents\n- [Technologies Used](#technologies-used)\n- [Architecture Diagram](#architecture-diagram)\n    - [Development](#diagram-development)\n- [Installation & Configuration](#installation-and-configuration)\n    - [Platform & Tools](#platform-and-tools)\n    - [Installation](#installation)\n- [Running App](#running-app)\n    - [Server](#server) \n    - [Ionic](#ionic) \n- [Building for iOS](#building-for-ios)\n- [Building for Android](#building-for-android)\n- [Vagrant](#vagrant)\n- [Ansible](#ansible)\n- [FAQ](#faq)\n- [License](#license)\n\n## Technologies Used\n| Mobile Side | Server Side | DevOps | \n|:-------------------:|:-------------------:|:-------------------:| \n| [Angular.js](http://angularjs.org/) ![Angularjs](https://avatars1.githubusercontent.com/u/139426?s=30) | [Node.js](http://nodejs.org/) <img src=\"http://nodejs.org/images/logo-light.svg\" height=\"30\" width=\"80\" /> | [Gulp](http://gulpjs.com/) ![Gulp](https://avatars0.githubusercontent.com/u/6200624?s=30) &nbsp; [Bower](http://bower.io/) ![Bower] (https://avatars3.githubusercontent.com/u/3709251?s=30) |\n[Ionic](http://ionicframework.com/) <img src=\"http://upload.wikimedia.org/wikipedia/commons/d/d1/Ionic_Logo.svg\" height=\"45\" width=\"80\" /> | [MongoDB](http://www.mongodb.org/) ![MongoDB] (https://avatars3.githubusercontent.com/u/45120?v=2&s=30) | [NPM](https://www.npmjs.org/) ![NPM] (https://avatars0.githubusercontent.com/u/6078720?s=30) &nbsp;                 [Ansible](https://www.ansible.com/) ![Ansible] (https://avatars3.githubusercontent.com/u/1507452?v=2&s=30) | \n[Material Design](https://material.angularjs.org/) ![Angularjs](https://avatars1.githubusercontent.com/u/139426?s=30) | [Express.js](http://expressjs.com/)<img src=\"https://cldup.com/wpGXm1cWwB.png\" height=\"40\" width=\"145\"> | [Vagrant](http://www.vagrantup.com/) <img src=\"https://www.vagrantup.com/images/logo_vagrant-81478652.png\" height=\"40\" width=\"145\"> |\n| [Cordova](https://cordova.apache.org/) <img src=\"https://cordova.apache.org/images/cordova_256.png\" height=\"35\" width=\"45\" /> | [Redis](http://redis.io/) <img src=\"http://upload.wikimedia.org/wikipedia/en/thumb/6/6b/Redis_Logo.svg/467px-Redis_Logo.svg.png?v=2&s=30\" height=\"35\" width=\"125\"> | \n\n## Architecture Diagram\n### <a name=\"diagram-development\"></a>Development\n![Ionic Photo Gallery Development Architecture Diagram](ionic-photo-gallery.jpg \"Ionic Photo Gallery Development Architecture Diagram\")\n\n## <a name=\"installation-and-configuration\"></a> Installation & Configuration\n### <a name=\"platform-and-tools\"></a> Platform & Tools\nYou need to have installed follow tools on your machine:\n\n- [Virtualbox](https://www.virtualbox.org/wiki/Downloads) 4.3.16+\n- [Vagrant](http://www.vagrantup.com/downloads.html) 1.6.2+\n- [Ansible](http://docs.ansible.com/intro_installation.html) 1.7.0+\n\n### <a name=\"installation\"></a> Installation\n\n**1.** Clone main repository:\n```bash\n$ git clone https://github.com/martinmicunda/ionic-photo-gallery.git\n$ cd ionic-photo-gallery\n```\n\n**2.** The following command would add a new `ubuntu trusty64 box`, and if an existing one is found, it will override it:\n\n```bash\n$ vagrant box add ubuntu/trusty64 --force\n```\n>**NOTE:** This process may take a while, as most Vagrant boxes will be at least **200 MB** big.\n\nVerify that box was installed by running the `list` subcommand that will list the boxes installed within Vagrant along with the provider that backs the box:\n\n```bash\n$ vagrant box list\nubuntu/trusty64  (virtualbox, 14.04)\n```\n**3.** The following command would install an `ansible roles` for this project, and if an existing one is found, it will override it:\n\n```bash\n$ bash bin/ansible-install-roles.sh\n```\nVerify that ansible roles were installed by running the `list` subcommand that will list the installed roles:\n\n```bash\n$ ansible-galaxy list\n- DavidWittman.redis, 1.0.3\n- laggyluke.direnv, v2.6.0\n- martinmicunda.common, v1.0.1\n- martinmicunda.ionic, v1.0.0\n- martinmicunda.nodejs, v1.0.1\n- nickp666.android-sdk, v0.0.1\n- Stouts.mongodb, 2.1.8\n- williamyeh.oracle-java, master\n```\n**4.** Now, run `vagrant up` that will create and provisioning `default` VM box. \n\n```bash\n$ vagrant up\n```\n>**NOTE:** **Vagrant will provision the virtual machine only once on the first run, any subsequent provisioning must be executed with the** `--provision` **flag either** `vagrant up --provision` **or** `vagrant reload --provision` **or** `vagrant provision` **if vagrant box is already running. The provisioning will re-run also if you destroy the VM and rebuild it with** `vagrant destroy` **and** `vagrant up` **.**\n\nIf there have been no errors when executing the above commands, the machines  `default` should be created. The following command would outputs status of the vagrant machine:\n\n```bash\n$ vagrant status\nCurrent machine states:\ndefault                   running (virtualbox)\n```\nNow you should be able to ssh into box:\n```bash\n$ vagrant ssh \n```\n## Running App\n### Server\n**1.** To start the server you need to ssh into box:\n```bash\n$ vagrant ssh\n```\n**2.** Install the server dependencies:\n```bash\n$ cd server\n$ npm install\n```\n**3.** Start the server:\n```bash\n$ npm start\n```\n>**NOTE:** **The [direnv](http://direnv.net/) is use as an environment variable manager so when you first time cd into server directory with a `.envrc` file in it, it will refuse to load the file. This is to protect you, since the contents of the .envrc will be executed by your shell, and they might come from untrusted sources. Simply run `direnv allow`, and it will trust that file until the next time it changes.**\n\n### Ionic\n**1.** To start the server you need to ssh into box:\n```bash\n$ vagrant ssh\n```\n**2.** Install the ionic dependencies:\n```bash\n$ cd ionic\n$ npm install\n```\n**3.** Start the ionic:\n```bash\n$ npm start\n```\nOpen up your browser and navigate to [http://127.0.0.1:8100](http://127.0.0.1:8100) and you should see ionic app up and running.\n\n## <a name=\"building-for-ios\"></a> Building for iOS\n**1.** ssh into box:\n```bash\n$ vagrant ssh\n```\n**2.** Add support for the iOS platform:\n```bash\n$ cd ionic\n$ ionic platform add ios\n```\n**3.** Build the project:\n```bash\n$ ionic build ios\n```\n**4.** Open `ionic-photo-gallery.xcodeproj` in the `ionic-photo-gallery/ionic/platforms/ios` folder.\n\n**5.** In [Xcode](https://developer.apple.com/xcode/), run the application on a device connected to your computer or in the iOS emulator.\n\n## <a name=\"building-for-android\"></a> Building for Android\n**1.** ssh into box:\n```bash\n$ vagrant ssh\n```\n**2.** Add support for the Android platform:\n```bash\n$ cd ionic\n$ ionic platform add android\n```\n**3.** Build the project:\n```bash\n$ ionic build android\n```\n\n**NOTE:** (martin) work in progress!!\n\n1. Start Genymotion\n2. Open Genymotion Shell\n3. Run follow command to get IP address\n\n```bash\n$ devices list\n```\nyou should see something like this:\n\n```bash\nGenymotion virtual device 0 is off. Please select a new virtual device with command : devices select\nAvailable devices:\n\n Id | Select |    Status     |   Type   |   IP Address    |      Name\n----+--------+---------------+----------+-----------------+---------------\n  0 |        |            On |  virtual |  192.168.58.101 | Samsung Galaxy S4 - 4.4.4 - API 19 - 1080x1920\n```\n5. Go to vagrant box using 'vagrant up' and 'vagrant ssh'.\n6. Type: `adb connect 192.168.56.101` and `adb devices`. You should see something like this:\n```\nvagrant@vagrant-ubuntu-trusty-64:~$ adb connect 192.168.58.101\nconnected to 192.168.58.101:5555\nvagrant@vagrant-ubuntu-trusty-64:~$ adb devices\nList of devices attached\n192.168.58.101:5555     device\n```\n7. Run `ionic run android`\n\n## Vagrant \nThere’s a ton of commands you can use to talk to Vagrant. For a full list see the [official docs](http://docs.vagrantup.com/v2/cli/), but here are the more common ones.\n\n* `vagrant up` - use this command to `start` your virtual environment\n* `vagrant halt` - use this command to `stop` your virtual environment\n* `vagrant suspend` - use this command to `pause` your virtual environment, make sure you do this before shutting down your computer to safely be able to restore the environment later.\n* `vagrant destroy` - use this command to `removes` your virtual environment from your machine\n* `vagrant reload` - use this command to your virtual environment, if you add the `--provision` flag, it will reprovision the box as well; this is useful with removing or adding things to the server via Ansible.\n* `vagrant ssh` - use this command to `connect` to the virtual server\n\n## Ansible\nTo get better understanding how Ansible works check the [official docs](http://docs.ansible.com/). Ansible installs the following software:\n\n* [git](http://git-scm.com/)\n* [node.js](https://nodejs.org/)\n* [npm](https://www.npmjs.com/)\n* [mongodb](https://www.mongodb.org/)\n* [redis](http://redis.io/)\n* [java 7](http://www.oracle.com/technetwork/java/javase/downloads/jre7-downloads-1880261.html)\n* [android SDK](https://developer.android.com/sdk/index.html)\n* [apache ant](http://ant.apache.org/)\n* [cordova](https://cordova.apache.org/)\n* [ionic CLI](http://ionicframework.com/docs/cli/)\n* [direnv](http://direnv.net/)\n\nThe `mongodb` and `redis` services are started after provisioning takes place.\n\n## FAQ\n### What if I want to uninstall application?\n**1.** The following command would permanently removes the `default` virtual box from your machine:\n```bash\n$ vagrant destroy\n```\n**2.** The following command would uninstall an `ansible roles` for this project:\n```bash\n$ bash bin/ansible-uninstall-roles.sh\n```\n\n**4.** The following command would remove  `trusty64 box`:\n```bash\n$ vagrant box remove trusty64\n```\n### What if I want a fresh install?\nIf you wish to destroy the `default` virtual boxe to make sure you have a fresh start, you can do these steps:\n```bash\n $ vagrant destroy \n $ vagrant up\n```\n\n## License\n\n    The MIT License\n    \n    Copyright (c) 2015 Martin Micunda  \n\n    Permission is hereby granted, free of charge, to any person obtaining a copy\n    of this software and associated documentation files (the \"Software\"), to deal\n    in the Software without restriction, including without limitation the rights\n    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n    copies of the Software, and to permit persons to whom the Software is\n    furnished to do so, subject to the following conditions:\n    \n    The above copyright notice and this permission notice shall be included in\n    all copies or substantial portions of the Software.\n    \n    THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n    THE SOFTWARE.\n"
  },
  {
    "path": "Vagrantfile",
    "content": "#####################################################################################\n# Vagrant Development Environment for Ionic applications.                           #\n#                                                                                   #\n# Author: Martin Micunda                                                            #\n#-----------------------------------------------------------------------------------#\n# Prerequisites: Virtualbox, Vagrant, Ansible                                       #\n# Usage: command 'vagrant up' in the folder of the Vagrantfile                      #\n#####################################################################################\n\n# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!\nVAGRANTFILE_API_VERSION = \"2\"\n\n# This Vagrant environment requires Vagrant 1.6.0 or higher.\nVagrant.require_version \">= 1.6.0\"\n\n#####################################################################################\n#                             VAGRANT MAGIC BEGINS HERE                             #\n#-----------------------------------------------------------------------------------#\n#          For full documentation on vagrant please visit www.vagrantup.com!        #\n#####################################################################################\n\nVagrant.configure(VAGRANTFILE_API_VERSION) do |config|\n    # Specify the base box\n    config.vm.box = \"ubuntu/trusty64\"\n\n    # ionic\n    config.vm.network \"forwarded_port\", guest: 8100, host: 8100\n    # livereload\n    config.vm.network \"forwarded_port\", guest: 35729, host: 35729\n    # server app\n    config.vm.network \"forwarded_port\", guest: 3000, host: 3000\n\n    config.vm.synced_folder \".\", \"/home/vagrant/ionic-photo-gallery\"\n\n    # Provision the VirtualBoxe with Ansible\n    config.vm.provision \"ansible\" do |ansible|\n        ansible.playbook = \"ansible/playbook.yml\"\n        ansible.raw_arguments = ['-v']\n    end\n\n    # Configure VM settings for server running in VirtualBox\n    config.vm.provider \"virtualbox\" do |vb|\n        # set the Video Ram to 128 megs\n        vb.customize [\"modifyvm\", :id, \"--vram\", \"128\"]\n        # turn on the USB drivers so that we can connect an Android device\n        vb.customize [\"modifyvm\", :id, \"--usb\", \"on\"]\n        # add a usb device filter for a Android Device\n        vb.customize [\"usbfilter\", \"add\", \"0\", \"--target\", :id, \"--name\", \"android\", \"--vendorid\", \"0x18d1\"]\n        # this is the name in the VirtualBox Manager UI\n        vb.name = \"IonicBox\"\n        # set the system memory for the virtual machine\n        vb.memory = 2048\n        # number of Physical CPUs to allocate\n        vb.cpus = 2\n    end\nend\n"
  },
  {
    "path": "ansible/playbook.yml",
    "content": "---\n- name: Install node.js, direnv, mongodb, redis, java, android-sdk and ionic\n  hosts: all\n  sudo: yes\n  roles:\n    - martinmicunda.common\n    - martinmicunda.nodejs\n    - martinmicunda.ionic\n    - laggyluke.direnv\n    - Stouts.mongodb\n    - DavidWittman.redis\n    - williamyeh.oracle-java\n    - nickpack.android_sdk\n  vars:\n    java_version: 7\n    android_sdk_download_location: http://dl.google.com/android/android-sdk_r24.1.2-linux.tgz\n    android_sdk_install_location: /home/vagrant/android-sdk-linux\n    android_sdk_dependency_packages:\n      - \"libncurses5\" #libncurses5:i386\n#      - \"libstdc++6\" #libstdc++6:i386\n      - \"lib32stdc++6\"\n      - \"zlib1g\" #zlib1g:i386\n      - \"lib32z1\"\n      - \"imagemagick\"\n      - \"expect\"\n      - \"ant\"\n      - \"ccache\"\n      - \"autoconf\"\n      - \"automake\"\n      - \"python-dev\"\n      - \"zlibc\"\n      - \"android-tools-adb\"\n    android_sdks_to_install: \"platform-tool,build-tools-22.0.1,build-tools-21.1.2,build-tools-20.0.0,build-tools-19.1.0,android-22,android-21,android-20,android-19\"\n\n"
  },
  {
    "path": "bin/ansible-install-roles.sh",
    "content": "#!/bin/bash\n\nset -e\nansible-galaxy install martinmicunda.common \\\n                       martinmicunda.nodejs \\\n                       martinmicunda.ionic \\\n                       laggyluke.direnv \\\n                       Stouts.mongodb \\\n                       DavidWittman.redis \\\n                       williamyeh.oracle-java \\\n                       nickpack.android_sdk \\\n                       --force\n"
  },
  {
    "path": "bin/ansible-uninstall-roles.sh",
    "content": "#!/bin/bash\n\nset -e\nansible-galaxy remove martinmicunda.common \\\n                      martinmicunda.nodejs \\\n                      martinmicunda.ionic \\\n                      laggyluke.direnv \\\n                      Stouts.mongodb \\\n                      DavidWittman.redis \\\n                      williamyeh.oracle-java \\\n                      nickpack.android_sdk"
  },
  {
    "path": "ionic/.bowerrc",
    "content": "{\n  \"directory\": \"www/lib\"\n}\n"
  },
  {
    "path": "ionic/.gitignore",
    "content": "# Specifies intentionally untracked files to ignore when using Git\n# http://git-scm.com/docs/gitignore\n\nnode_modules/\nplatforms/\nplugins/\nwww/lib\n"
  },
  {
    "path": "ionic/bower.json",
    "content": "{\n    \"name\": \"ionic-photo-gallery\",\n    \"private\": \"true\",\n    \"devDependencies\": {\n        \"ionic\": \"driftyco/ionic-bower#1.0.0-rc.2\",\n        \"restangular\": \"1.4.0\",\n        \"angular-local-storage\": \"0.1.5\",\n        \"angular-material\": \"0.8.3\",\n        \"angular-messages\": \"1.3.13\"\n    },\n    \"dependencies\": {\n        \"ngCordova\": \"~0.1.14-alpha\"\n    }\n}\n"
  },
  {
    "path": "ionic/config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<widget id=\"com.martinmicunda.photo-gallery\" version=\"0.0.1\" xmlns=\"http://www.w3.org/ns/widgets\" xmlns:cdv=\"http://cordova.apache.org/ns/1.0\">\n  <name>ionic-photo-gallery</name>\n  <description>\n        A hybrid app with authentication that allows registered users view a gallery of photos they have uploaded via the camera phone.\n    </description>\n  <author email=\"martinmicunda@hotmail.com\" href=\"http://martinmicunda.com/\">\n        Martin Micunda\n    </author>\n  <content src=\"index.html\"/>\n  <access origin=\"*\"/>\n  <preference name=\"webviewbounce\" value=\"false\"/>\n  <preference name=\"UIWebViewBounce\" value=\"false\"/>\n  <preference name=\"DisallowOverscroll\" value=\"true\"/>\n  <preference name=\"BackupWebStorage\" value=\"none\"/>\n  <preference name=\"SplashScreen\" value=\"screen\"/>\n  <preference name=\"SplashScreenDelay\" value=\"3000\"/>\n  <feature name=\"StatusBar\">\n    <param name=\"ios-package\" value=\"CDVStatusBar\" onload=\"true\"/>\n  </feature>\n  <platform name=\"android\">\n    <icon src=\"resources/android/icon/drawable-ldpi-icon.png\" density=\"ldpi\"/>\n    <icon src=\"resources/android/icon/drawable-mdpi-icon.png\" density=\"mdpi\"/>\n    <icon src=\"resources/android/icon/drawable-hdpi-icon.png\" density=\"hdpi\"/>\n    <icon src=\"resources/android/icon/drawable-xhdpi-icon.png\" density=\"xhdpi\"/>\n    <icon src=\"resources/android/icon/drawable-xxhdpi-icon.png\" density=\"xxhdpi\"/>\n    <icon src=\"resources/android/icon/drawable-xxxhdpi-icon.png\" density=\"xxxhdpi\"/>\n    <splash src=\"resources/android/splash/drawable-land-ldpi-screen.png\" density=\"land-ldpi\"/>\n    <splash src=\"resources/android/splash/drawable-land-mdpi-screen.png\" density=\"land-mdpi\"/>\n    <splash src=\"resources/android/splash/drawable-land-hdpi-screen.png\" density=\"land-hdpi\"/>\n    <splash src=\"resources/android/splash/drawable-land-xhdpi-screen.png\" density=\"land-xhdpi\"/>\n    <splash src=\"resources/android/splash/drawable-land-xxhdpi-screen.png\" density=\"land-xxhdpi\"/>\n    <splash src=\"resources/android/splash/drawable-land-xxxhdpi-screen.png\" density=\"land-xxxhdpi\"/>\n    <splash src=\"resources/android/splash/drawable-port-ldpi-screen.png\" density=\"port-ldpi\"/>\n    <splash src=\"resources/android/splash/drawable-port-mdpi-screen.png\" density=\"port-mdpi\"/>\n    <splash src=\"resources/android/splash/drawable-port-hdpi-screen.png\" density=\"port-hdpi\"/>\n    <splash src=\"resources/android/splash/drawable-port-xhdpi-screen.png\" density=\"port-xhdpi\"/>\n    <splash src=\"resources/android/splash/drawable-port-xxhdpi-screen.png\" density=\"port-xxhdpi\"/>\n    <splash src=\"resources/android/splash/drawable-port-xxxhdpi-screen.png\" density=\"port-xxxhdpi\"/>\n  </platform>\n  <platform name=\"ios\">\n    <icon src=\"resources/ios/icon/icon.png\" width=\"57\" height=\"57\"/>\n    <icon src=\"resources/ios/icon/icon@2x.png\" width=\"114\" height=\"114\"/>\n    <icon src=\"resources/ios/icon/icon-40.png\" width=\"40\" height=\"40\"/>\n    <icon src=\"resources/ios/icon/icon-40@2x.png\" width=\"80\" height=\"80\"/>\n    <icon src=\"resources/ios/icon/icon-50.png\" width=\"50\" height=\"50\"/>\n    <icon src=\"resources/ios/icon/icon-50@2x.png\" width=\"100\" height=\"100\"/>\n    <icon src=\"resources/ios/icon/icon-60.png\" width=\"60\" height=\"60\"/>\n    <icon src=\"resources/ios/icon/icon-60@2x.png\" width=\"120\" height=\"120\"/>\n    <icon src=\"resources/ios/icon/icon-60@3x.png\" width=\"180\" height=\"180\"/>\n    <icon src=\"resources/ios/icon/icon-72.png\" width=\"72\" height=\"72\"/>\n    <icon src=\"resources/ios/icon/icon-72@2x.png\" width=\"144\" height=\"144\"/>\n    <icon src=\"resources/ios/icon/icon-76.png\" width=\"76\" height=\"76\"/>\n    <icon src=\"resources/ios/icon/icon-76@2x.png\" width=\"152\" height=\"152\"/>\n    <icon src=\"resources/ios/icon/icon-small.png\" width=\"29\" height=\"29\"/>\n    <icon src=\"resources/ios/icon/icon-small@2x.png\" width=\"58\" height=\"58\"/>\n    <icon src=\"resources/ios/icon/icon-small@3x.png\" width=\"87\" height=\"87\"/>\n    <splash src=\"resources/ios/splash/Default-568h@2x~iphone.png\" height=\"1136\" width=\"640\"/>\n    <splash src=\"resources/ios/splash/Default-667h.png\" height=\"1334\" width=\"750\"/>\n    <splash src=\"resources/ios/splash/Default-736h.png\" height=\"2208\" width=\"1242\"/>\n    <splash src=\"resources/ios/splash/Default-Landscape-736h.png\" height=\"1242\" width=\"2208\"/>\n    <splash src=\"resources/ios/splash/Default-Landscape@2x~ipad.png\" height=\"1536\" width=\"2048\"/>\n    <splash src=\"resources/ios/splash/Default-Landscape~ipad.png\" height=\"768\" width=\"1024\"/>\n    <splash src=\"resources/ios/splash/Default-Portrait@2x~ipad.png\" height=\"2048\" width=\"1536\"/>\n    <splash src=\"resources/ios/splash/Default-Portrait~ipad.png\" height=\"1024\" width=\"768\"/>\n    <splash src=\"resources/ios/splash/Default@2x~iphone.png\" height=\"960\" width=\"640\"/>\n    <splash src=\"resources/ios/splash/Default~iphone.png\" height=\"480\" width=\"320\"/>\n  </platform>\n</widget>\n"
  },
  {
    "path": "ionic/gulpfile.js",
    "content": "var gulp = require('gulp');\nvar gutil = require('gulp-util');\nvar bower = require('bower');\nvar concat = require('gulp-concat');\nvar sass = require('gulp-sass');\nvar minifyCss = require('gulp-minify-css');\nvar rename = require('gulp-rename');\nvar sh = require('shelljs');\n\nvar paths = {\n  sass: ['./scss/**/*.scss']\n};\n\ngulp.task('default', ['sass']);\n\ngulp.task('sass', function(done) {\n  gulp.src('./scss/ionic.app.scss')\n    .pipe(sass())\n    .pipe(gulp.dest('./www/css/'))\n    .pipe(minifyCss({\n      keepSpecialComments: 0\n    }))\n    .pipe(rename({ extname: '.min.css' }))\n    .pipe(gulp.dest('./www/css/'))\n    .on('end', done);\n});\n\ngulp.task('watch', function() {\n  gulp.watch(paths.sass, ['sass']);\n});\n\ngulp.task('install', ['git-check'], function() {\n  return bower.commands.install()\n    .on('log', function(data) {\n      gutil.log('bower', gutil.colors.cyan(data.id), data.message);\n    });\n});\n\ngulp.task('git-check', function(done) {\n  if (!sh.which('git')) {\n    console.log(\n      '  ' + gutil.colors.red('Git is not installed.'),\n      '\\n  Git, the version control system, is required to download Ionic.',\n      '\\n  Download git here:', gutil.colors.cyan('http://git-scm.com/downloads') + '.',\n      '\\n  Once git is installed, run \\'' + gutil.colors.cyan('gulp install') + '\\' again.'\n    );\n    process.exit(1);\n  }\n  done();\n});\n"
  },
  {
    "path": "ionic/hooks/README.md",
    "content": "<!--\n#\n# Licensed to the Apache Software Foundation (ASF) under one\n# or more contributor license agreements.  See the NOTICE file\n# distributed with this work for additional information\n# regarding copyright ownership.  The ASF licenses this file\n# to you under the Apache License, Version 2.0 (the\n# \"License\"); you may not use this file except in compliance\n# with the License.  You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing,\n# software distributed under the License is distributed on an\n# \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n#  KIND, either express or implied.  See the License for the\n# specific language governing permissions and limitations\n# under the License.\n#\n-->\n# Cordova Hooks\n\nThis directory may contain scripts used to customize cordova commands. This\ndirectory used to exist at `.cordova/hooks`, but has now been moved to the\nproject root. Any scripts you add to these directories will be executed before\nand after the commands corresponding to the directory name. Useful for\nintegrating your own build systems or integrating with version control systems.\n\n__Remember__: Make your scripts executable.\n\n## Hook Directories\nThe following subdirectories will be used for hooks:\n\n    after_build/\n    after_compile/\n    after_docs/\n    after_emulate/\n    after_platform_add/\n    after_platform_rm/\n    after_platform_ls/\n    after_plugin_add/\n    after_plugin_ls/\n    after_plugin_rm/\n    after_plugin_search/\n    after_prepare/\n    after_run/\n    after_serve/\n    before_build/\n    before_compile/\n    before_docs/\n    before_emulate/\n    before_platform_add/\n    before_platform_rm/\n    before_platform_ls/\n    before_plugin_add/\n    before_plugin_ls/\n    before_plugin_rm/\n    before_plugin_search/\n    before_prepare/\n    before_run/\n    before_serve/\n    pre_package/ <-- Windows 8 and Windows Phone only.\n\n## Script Interface\n\nAll scripts are run from the project's root directory and have the root directory passes as the first argument. All other options are passed to the script using environment variables:\n\n* CORDOVA_VERSION - The version of the Cordova-CLI.\n* CORDOVA_PLATFORMS - Comma separated list of platforms that the command applies to (e.g.: android, ios).\n* CORDOVA_PLUGINS - Comma separated list of plugin IDs that the command applies to (e.g.: org.apache.cordova.file, org.apache.cordova.file-transfer)\n* CORDOVA_HOOK - Path to the hook that is being executed.\n* CORDOVA_CMDLINE - The exact command-line arguments passed to cordova (e.g.: cordova run ios --emulate)\n\nIf a script returns a non-zero exit code, then the parent cordova command will be aborted.\n\n\n## Writing hooks\n\nWe highly recommend writting your hooks using Node.js so that they are\ncross-platform. Some good examples are shown here:\n\n[http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/](http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/)\n\n"
  },
  {
    "path": "ionic/hooks/after_platform_add/010_install_plugins.js",
    "content": "#!/usr/bin/env node\n\n/**\n * Install all plugins listed in package.json\n * https://raw.githubusercontent.com/diegonetto/generator-ionic/master/templates/hooks/after_platform_add/install_plugins.js\n */\nvar exec = require('child_process').exec;\nvar path = require('path');\nvar sys = require('sys');\n\nvar packageJSON = null;\n\ntry {\n  packageJSON = require('../../package.json');\n} catch(ex) {\n  console.log('\\nThere was an error fetching your package.json file.')\n  console.log('\\nPlease ensure a valid package.json is in the root of this project\\n')\n  return;\n}\n\nvar cmd = process.platform === 'win32' ? 'cordova.cmd' : 'cordova';\n// var script = path.resolve(__dirname, '../../node_modules/cordova/bin', cmd);\n\npackageJSON.cordovaPlugins = packageJSON.cordovaPlugins || [];\npackageJSON.cordovaPlugins.forEach(function (plugin) {\n  exec('cordova plugin add ' + plugin, function (error, stdout, stderr) {\n    sys.puts(stdout);\n  });\n});\n"
  },
  {
    "path": "ionic/hooks/after_plugin_add/010_register_plugin.js",
    "content": "#!/usr/bin/env node\n\n/**\n * Push plugins to cordovaPlugins array after_plugin_add\n */\nvar fs = require('fs'),\n    packageJSON = require('../../package.json'),\n    path = require('path');\n\npackageJSON.cordovaPlugins = packageJSON.cordovaPlugins || [];\nprocess.env.CORDOVA_PLUGINS.split(',').forEach(function (plugin) {\n  var configString,\n      idRegEx,\n      id,\n      pluginXmlPath,\n      pluginToAdd;\n\n  if(plugin.indexOf('https') != -1 || plugin.indexOf('git') != -1) {\n    console.log('Installing plugin from url');\n  }\n\n  if(plugin.indexOf('/') != -1) {\n    try {\n      pluginXmlPath = path.resolve(plugin, 'plugin.xml');\n      console.log('got pluginXmlPath:', pluginXmlPath);\n      if (!fs.existsSync(pluginXmlPath)) {\n        var errorMessage = ['There was no plugin.xml file found for path: ', pluginXmlPath].join('');\n        return;\n      }\n\n      configString = fs.readFileSync(pluginXmlPath,{encoding: 'utf8'});\n      idRegEx = new RegExp('<plugin[^>]*id=\"(.*)\"', 'i');\n      id = idRegEx.exec(configString)[1]\n      pluginToAdd = {id: id, locator: plugin};\n    } catch(ex) {\n      console.log('There was an error retrieving the plugin.xml filr from the 010_register_plugin.js hook', ex);\n    }\n  } else {\n    pluginToAdd = plugin;\n  }\n\n  if(typeof pluginToAdd == 'string' && packageJSON.cordovaPlugins.indexOf(pluginToAdd) == -1) {\n    packageJSON.cordovaPlugins.push(pluginToAdd);\n  } else if (typeof pluginToAdd == 'object') {\n    var pluginExists = false;\n    packageJSON.cordovaPlugins.forEach(function(checkPlugin) {\n      if(typeof checkPlugin == 'object' && checkPlugin.id == pluginToAdd.id) {\n        pluginExists = true;\n      }\n    })\n    if(!pluginExists) {\n      packageJSON.cordovaPlugins.push(pluginToAdd);\n    }\n  }\n});\n\nfs.writeFileSync('package.json', JSON.stringify(packageJSON, null, 2));\n"
  },
  {
    "path": "ionic/hooks/after_plugin_rm/010_deregister_plugin.js",
    "content": "#!/usr/bin/env node\n\n/**\n * Remove plugins from cordovaPlugins array after_plugin_rm\n */\nvar fs = require('fs');\nvar packageJSON = require('../../package.json');\n\npackageJSON.cordovaPlugins = packageJSON.cordovaPlugins || [];\n\nprocess.env.CORDOVA_PLUGINS.split(',').forEach(function (plugin) {\n  var index = packageJSON.cordovaPlugins.indexOf(plugin);\n  if (index > -1) {\n    packageJSON.cordovaPlugins.splice(index, 1);\n  } else {\n    //If it didnt find a match, it may be listed as {id,locator}\n    for(var i = 0, j = packageJSON.cordovaPlugins.length; i < j; i++) {\n      var packagePlugin = packageJSON.cordovaPlugins[i];\n      if(typeof packagePlugin == 'object' && packagePlugin.id == plugin) {\n        packageJSON.cordovaPlugins.splice(index, 1);\n        break;\n      }\n    }\n  }\n});\n\nfs.writeFile('package.json', JSON.stringify(packageJSON, null, 2));\n"
  },
  {
    "path": "ionic/hooks/after_prepare/010_add_platform_class.js",
    "content": "#!/usr/bin/env node\n\n// Add Platform Class\n// v1.0\n// Automatically adds the platform class to the body tag\n// after the `prepare` command. By placing the platform CSS classes\n// directly in the HTML built for the platform, it speeds up\n// rendering the correct layout/style for the specific platform\n// instead of waiting for the JS to figure out the correct classes.\n\nvar fs = require('fs');\nvar path = require('path');\n\nvar rootdir = process.argv[2];\n\nfunction addPlatformBodyTag(indexPath, platform) {\n  // add the platform class to the body tag\n  try {\n    var platformClass = 'platform-' + platform;\n    var cordovaClass = 'platform-cordova platform-webview';\n\n    var html = fs.readFileSync(indexPath, 'utf8');\n\n    var bodyTag = findBodyTag(html);\n    if(!bodyTag) return; // no opening body tag, something's wrong\n\n    if(bodyTag.indexOf(platformClass) > -1) return; // already added\n\n    var newBodyTag = bodyTag;\n\n    var classAttr = findClassAttr(bodyTag);\n    if(classAttr) {\n      // body tag has existing class attribute, add the classname\n      var endingQuote = classAttr.substring(classAttr.length-1);\n      var newClassAttr = classAttr.substring(0, classAttr.length-1);\n      newClassAttr += ' ' + platformClass + ' ' + cordovaClass + endingQuote;\n      newBodyTag = bodyTag.replace(classAttr, newClassAttr);\n\n    } else {\n      // add class attribute to the body tag\n      newBodyTag = bodyTag.replace('>', ' class=\"' + platformClass + ' ' + cordovaClass + '\">');\n    }\n\n    html = html.replace(bodyTag, newBodyTag);\n\n    fs.writeFileSync(indexPath, html, 'utf8');\n\n    process.stdout.write('add to body class: ' + platformClass + '\\n');\n  } catch(e) {\n    process.stdout.write(e);\n  }\n}\n\nfunction findBodyTag(html) {\n  // get the body tag\n  try{\n    return html.match(/<body(?=[\\s>])(.*?)>/gi)[0];\n  }catch(e){}\n}\n\nfunction findClassAttr(bodyTag) {\n  // get the body tag's class attribute\n  try{\n    return bodyTag.match(/ class=[\"|'](.*?)[\"|']/gi)[0];\n  }catch(e){}\n}\n\nif (rootdir) {\n\n  // go through each of the platform directories that have been prepared\n  var platforms = (process.env.CORDOVA_PLATFORMS ? process.env.CORDOVA_PLATFORMS.split(',') : []);\n\n  for(var x=0; x<platforms.length; x++) {\n    // open up the index.html file at the www root\n    try {\n      var platform = platforms[x].trim().toLowerCase();\n      var indexPath;\n\n      if(platform == 'android') {\n        indexPath = path.join('platforms', platform, 'assets', 'www', 'index.html');\n      } else {\n        indexPath = path.join('platforms', platform, 'www', 'index.html');\n      }\n\n      if(fs.existsSync(indexPath)) {\n        addPlatformBodyTag(indexPath, platform);\n      }\n\n    } catch(e) {\n      process.stdout.write(e);\n    }\n  }\n\n}\n"
  },
  {
    "path": "ionic/hooks/after_prepare/020_remove_sass_from_platforms.js",
    "content": "#!/usr/bin/env node\n\n/**\n * After prepare, files are copied to the platforms/ios and platforms/android folders.\n * Lets clean up some of those files that arent needed with this hook.\n */\nvar fs = require('fs');\nvar path = require('path');\n\nvar deleteFolderRecursive = function(removePath) {\n  if( fs.existsSync(removePath) ) {\n    fs.readdirSync(removePath).forEach(function(file,index){\n      var curPath = path.join(removePath, file);\n      if(fs.lstatSync(curPath).isDirectory()) { // recurse\n        deleteFolderRecursive(curPath);\n      } else { // delete file\n        fs.unlinkSync(curPath);\n      }\n    });\n    fs.rmdirSync(removePath);\n  }\n};\n\nvar iosPlatformsDir = path.resolve(__dirname, '../../platforms/ios/www/lib/ionic/scss');\nvar androidPlatformsDir = path.resolve(__dirname, '../../platforms/android/assets/www/lib/ionic/scss');\n\ndeleteFolderRecursive(iosPlatformsDir);\ndeleteFolderRecursive(androidPlatformsDir);\n"
  },
  {
    "path": "ionic/hooks/before_platform_add/init_directories.js",
    "content": "#!/usr/bin/env node\n\n/**\n * On a fresh clone, the local platforms/ and plugins/ directories will be\n * missing, so ensure they get created before the first platform is added.\n */\nvar fs = require('fs');\nvar path = require('path');\n\nvar platformsDir = path.resolve(__dirname, '../../platforms');\nvar pluginsDir = path.resolve(__dirname, '../../plugins');\n\ntry {\n  fs.mkdirSync(platformsDir, function (err) {\n    if (err) { console.error(err); }\n  });\n} catch(ex) {}\n\ntry {\n  fs.mkdirSync(pluginsDir, function (err) {\n    if (err) { console.error(err); }\n  });\n} catch(ex) {}\n"
  },
  {
    "path": "ionic/ionic.project",
    "content": "{\n  \"name\": \"ionic-photo-gallery\",\n  \"app_id\": \"2e3df8d5\"\n}"
  },
  {
    "path": "ionic/package.json",
    "content": "{\n  \"name\": \"ionic-photo-gallery-ionic\",\n  \"version\": \"0.0.0\",\n  \"description\": \"A hybrid app with authentication that allows registered users view a gallery of photos they have uploaded via the camera phone.\",\n  \"author\": {\n    \"name\": \"Martin Micunda\",\n    \"url\": \"http://martinmicunda.com\"\n  },\n  \"private\": true,\n  \"scripts\": {\n    \"start\": \"ionic serve -s -c --lab\",\n    \"postinstall\": \"bower install\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/martinmicunda/ionic-photo-gallery\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/martinmicunda/ionic-photo-gallery/issues\"\n  },\n  \"devDependencies\": {\n    \"gulp\": \"^3.5.6\",\n    \"gulp-sass\": \"^0.7.1\",\n    \"gulp-concat\": \"^2.2.0\",\n    \"gulp-minify-css\": \"^0.3.0\",\n    \"gulp-rename\": \"^1.2.0\",\n    \"bower\": \"^1.3.3\",\n    \"gulp-util\": \"^2.2.14\",\n    \"shelljs\": \"^0.3.0\"\n  },\n  \"cordovaPlugins\": [\n    \"org.apache.cordova.camera\",\n    \"org.apache.cordova.vibration\",\n    \"org.apache.cordova.file-transfer\",\n    \"org.apache.cordova.dialogs\"\n  ],\n  \"cordovaPlatforms\": [\n    \"android\"\n  ]\n}"
  },
  {
    "path": "ionic/scss/ionic.app.scss",
    "content": "/*\nTo customize the look and feel of Ionic, you can override the variables\nin ionic's _variables.scss file.\n\nFor example, you might change some of the default colors:\n\n$light:                           #fff !default;\n$stable:                          #f8f8f8 !default;\n$positive:                        #387ef5 !default;\n$calm:                            #11c1f3 !default;\n$balanced:                        #33cd5f !default;\n$energized:                       #ffc900 !default;\n$assertive:                       #ef473a !default;\n$royal:                           #886aea !default;\n$dark:                            #444 !default;\n*/\n\n// The path for our ionicons font files, relative to the built CSS in www/css\n$ionicons-font-path: \"../lib/ionic/fonts\" !default;\n\n// Include all of Ionic\n@import \"../www/lib/ionic/scss/ionic\";\n\n"
  },
  {
    "path": "ionic/test/.gitKeep",
    "content": ""
  },
  {
    "path": "ionic/www/css/style.css",
    "content": ".signin-header, .signup-header {\n    border-top: 2px solid rgb(0,150,136);\n}\n\n.circle {\n    width:500px;\n    height:500px;\n    border-radius:50%;\n    font-size:50px;\n    color:#fff;\n    line-height:130px;\n    text-align:center;\n    background-color: rgb(0,150,136);\n    padding: 30px;\n}\n\n.text-center {\n    text-align: center;\n}\n\n.pb-50 {\n    padding-bottom: 50px;\n}\n.pt-10 {\n    padding-top: 10px;\n}\n.pseudo-link {\n    color: black;\n    font-size: 15px;\n    text-decoration: none;\n}\n.profile-view .item-avatar {\n    padding-left: 0px;\n}\n.profile-view .item-avatar > img:first-child {\n    max-width: 60px;\n    max-height: 60px;\n    text-align: center;\n    position: relative;\n    left: 0;\n    margin-bottom: 15px;\n}\n/* vertical center */\n/*.scroll-content {*/\n    /*display: table !important;*/\n    /*width: 100% !important;*/\n    /*height: 100% !important;*/\n/*}*/\n/*.scroll {*/\n    /*display: table-cell;*/\n    /*vertical-align: middle;*/\n    /*text-align: center;*/\n/*}*/\na {\n    color: #333;\n    text-decoration: none;\n    outline: 0;\n}\n.link-button {\n    color: #333;\n    border: 0;\n    background: none;\n    padding: 0;\n}\n.text-small {\n    font-size: 12px;\n}\n.text-muted {\n    color: #777;\n}\n\n\n/* fix ionic border input issue */\ntextarea, input[type=\"text\"], input[type=\"password\"], input[type=\"datetime\"], input[type=\"datetime-local\"], input[type=\"date\"], input[type=\"month\"], input[type=\"time\"], input[type=\"week\"], input[type=\"number\"], input[type=\"email\"], input[type=\"url\"], input[type=\"search\"], input[type=\"tel\"], input[type=\"color\"] {\n    border: 1px solid rgba(0, 0, 0, 0.117647);\n    border-right-width: 0;\n    border-top-width: 0;\n    border-left-width: 0;\n}\n\nmd-input-container.md-default-theme:not(.md-input-invalid).md-input-focused md-icon {\n    fill: rgb(0,150,136)\n}\n\nmd-input-container.md-input-invalid > md-icon {\n    fill: rgb(244,67,54)\n}\n.messages {\n    margin-left: 56px;\n}\n\n\n.bar.bar-positive {\n    background-color: rgb(0,150,136);\n    border-color: #00796B;\n    background-image: linear-gradient(0deg, #00796B, #00796B 50%, transparent 50%);\n}\n\n.heading-item {\n    background-color: #00796B;\n}\n.heading-item .greeting {\n    color: #fff;\n    font-weight: bold;\n}\n.heading-item .message {\n    color: #fff;\n}\n.item-avatar {\n    padding-top: 25px;\n    min-height: 80px;\n}\n.item-avatar > img:first-child {\n    border: 1px solid #fff;\n    top: 25px;\n}\n.sidebar .sidebar-nav {\n    margin: 0;\n    padding: 0;\n}\n.sidebar .sidebar-nav li {\n    position: relative;\n    list-style-type: none;\n}\n.sidebar-default .sidebar-nav li > a {\n    background-color: transparent;\n    font-family: 'RobotoDraft', 'Roboto', 'Helvetica Neue, Helvetica, Arial', sans-serif;\n    font-style: normal;\n    font-weight: 300;\n    font-size: 14px;\n    line-height: 1.4;\n}\n.sidebar .sidebar-nav li a {\n    position: relative;\n    cursor: pointer;\n    user-select: none;\n    display: block;\n    height: 48px;\n    line-height: 48px;\n    padding: 0;\n    padding-left: 16px;\n    padding-right: 56px;\n    text-decoration: none;\n    clear: both;\n    font-weight: 500;\n    overflow: hidden;\n    -o-text-overflow: ellipsis;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n    -webkit-transition: all 0.2s ease-in-out;\n    -o-transition: all 0.2s ease-in-out;\n    transition: all 0.2s ease-in-out;\n}\n.sidebar-nav li > a i {\n    color: #757575;\n}\n.sidebar .sidebar-icon {\n    display: inline-block;\n    margin-right: 16px;\n    min-width: 40px;\n    width: 40px;\n    text-align: left;\n    font-size: 20px;\n    font-weight: 300;\n    color: #333;\n}\n.sidebar-nav .divider {\n    background-color: #bdbdbd;\n}\n.sidebar .sidebar-divider, .sidebar .sidebar-nav .divider {\n    position: relative;\n    display: block;\n    height: 1px;\n    margin: 8px 0;\n    padding: 0;\n    overflow: hidden;\n}\n\n.item.activated {\n    background-color: transparent;\n}\n.camera {\n    position: absolute;\n    bottom: 20px;\n    right: 20px;\n}\n.camera i {\n    font-size: 30px;\n}\n\n\n/* Galleries view */\n.galleries-list {\n    flex-wrap: wrap;\n    -webkit-flex-flow: row wrap;\n}\n.gallery {\n    height: calc(50vw - 15px);\n    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);\n    border-radius: 2px;\n    background-color: #FFF;\n    position: relative;\n    display: block;\n}\n.gallery .gallery-image {\n    width: 100%;\n    height: 100%;\n    border-radius: 2px;\n}\n.gallery .gallery-bg {\n    position: absolute;\n    top: 0px;\n    left: 0px;\n    width: 100%;\n    height: 100%;\n    background: rgba(42, 42, 42, 0.3);\n}\n.gallery .gallery-title, .galleries-view .gallery .gallery-title {\n    position: absolute;\n    bottom: 0;\n    left: 0;\n    width: 100%;\n    height: 20%;\n    background: rgba(0,0,0,0.5);\n    color: #fff;\n}\n.galleries-view .gallery .gallery-title {\n    padding: 3px 5px;\n}\n.gallery-view .gallery .gallery-title {\n    text-align: center;\n}\n.gallery .gallery-title i {\n    font-size: 2em;\n}\n.gallery .gallery-title h5, .gallery .gallery-title h6 {\n    color: #fff;\n    margin: 0;\n}\n\n/* TODO: Remove this once issue with ngMaterial is fix */\n.md-button.md-default-theme.md-primary.md-fab, .md-button.md-default-theme.md-primary {\n    color: rgb(255,255,255);\n    background-color: rgb(0,150,136);\n}\nmd-input-container:not(.md-input-has-value) input:not(:focus) {\n    color: inherit;\n}\nmd-icon i {\n    padding-left: 15px;\n    font-size: 20px;\n}\n.messages {\n    color: red;\n}\nmd-input-container > md-icon {\n    top: 10px;\n}\n"
  },
  {
    "path": "ionic/www/index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n    <!--<base href=\"/\">-->\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width\">\n    <title></title>\n\n    <link href=\"lib/ionic/css/ionic.css\" rel=\"stylesheet\">\n    <link href=\"lib/angular-material/angular-material.css\" rel=\"stylesheet\">\n    <link href=\"css/style.css\" rel=\"stylesheet\">\n\n    <!-- IF using Sass (run gulp sass first), then uncomment below and remove the CSS includes above\n    <link href=\"css/ionic.app.css\" rel=\"stylesheet\">\n    -->\n\n</head>\n    <body ng-app=\"app\">\n        <ion-nav-view></ion-nav-view>\n\n        <!-- ionic/angularjs js -->\n        <script src=\"lib/ionic/js/ionic.bundle.js\"></script>\n        <!--<script src=\"lib/angular/angular.js\"></script>-->\n        <!--<script src=\"lib/angular-animate/angular-animate.js\"></script>-->\n        <!--<script src=\"lib/angular-sanitize/angular-sanitize.js\"></script>-->\n        <!--<script src=\"lib/angular-ui-router/release/angular-ui-router.js\"></script>-->\n        <!--<script src=\"lib/ionic/js/ionic.js\"></script>-->\n        <!--<script src=\"lib/ionic/js/ionic-angular.js\"></script>-->\n        <script src=\"lib/lodash/dist/lodash.compat.js\"></script>\n        <script src=\"lib/restangular/dist/restangular.js\"></script>\n        <script src=\"lib/angular-local-storage/dist/angular-local-storage.js\"></script>\n        <script src=\"lib/angular-aria/angular-aria.js\"></script>\n        <script src=\"lib/angular-messages/angular-messages.js\"></script>\n        <!--<script src=\"lib/angular-material/angular-material.js\"></script>-->\n\n        <script src=\"lib/ngCordova/dist/ng-cordova.js\"></script>\n\n        <!-- cordova script (this will be a 404 during development) -->\n        <script src=\"cordova.js\"></script>\n\n        <!-- your app's js -->\n        <script src=\"js/app.js\"></script>\n        <script src=\"js/core/core.js\"></script>\n        <script src=\"js/core/config/config.js\"></script>\n        <script src=\"js/core/services/user/user.service.js\"></script>\n        <script src=\"js/core/services/image/image.service.js\"></script>\n        <script src=\"js/core/services/camera/camera.service.js\"></script>\n        <script src=\"js/core/services/authentication/authentication.service.js\"></script>\n        <script src=\"js/core/services/authentication/base64.service.js\"></script>\n        <script src=\"js/core/services/authentication/interceptor.service.js\"></script>\n        <script src=\"js/core/services/authentication/token.service.js\"></script>\n        <script src=\"js/core/services/error/error.service.js\"></script>\n        <script src=\"js/routes/layout/layout.js\"></script>\n        <script src=\"js/routes/layout/layout.route.js\"></script>\n        <script src=\"js/routes/layout/layout.controller.js\"></script>\n        <script src=\"js/routes/gallery/gallery.js\"></script>\n        <script src=\"js/routes/gallery/gallery.route.js\"></script>\n        <script src=\"js/routes/gallery/gallery.controller.js\"></script>\n        <script src=\"js/routes/galleries/galleries.js\"></script>\n        <script src=\"js/routes/galleries/galleries.route.js\"></script>\n        <script src=\"js/routes/galleries/galleries.controller.js\"></script>\n        <script src=\"js/routes/signin/signin.js\"></script>\n        <script src=\"js/routes/signin/signin.route.js\"></script>\n        <script src=\"js/routes/signin/signin.controller.js\"></script>\n        <script src=\"js/routes/signup/signup.js\"></script>\n        <script src=\"js/routes/signup/signup.route.js\"></script>\n        <script src=\"js/routes/signup/signup.controller.js\"></script>\n        <script src=\"js/routes/user/user.js\"></script>\n        <script src=\"js/routes/user/user.route.js\"></script>\n        <script src=\"js/routes/user/user.controller.js\"></script>\n        <script src=\"js/routes/users/users.js\"></script>\n        <script src=\"js/routes/users/users.route.js\"></script>\n        <script src=\"js/routes/users/users.controller.js\"></script>\n    </body>\n</html>\n"
  },
  {
    "path": "ionic/www/js/app.js",
    "content": "/**\n * Main app module.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license   The MIT License {@link http://opensource.org/licenses/MIT}\n */\n(function () {\n    'use strict';\n\n    angular.module('app', [\n        // angular modules\n        'ngAnimate',\n        'ngSanitize',\n        'ngMessages',\n        //'ngMaterial',\n\n        // 3rd party modules\n        'ui.router',\n        'ionic',\n        'restangular',\n        'LocalStorageModule',\n        'ngCordova',\n\n        // app modules\n        'app.core',\n        'app.layout',\n        'app.signup',\n        'app.signin',\n        'app.user',\n        'app.users',\n        'app.gallery',\n        'app.galleries'\n    ]);\n\n})();\n"
  },
  {
    "path": "ionic/www/js/components/.gitkeep",
    "content": ""
  },
  {
    "path": "ionic/www/js/core/config/config.js",
    "content": "/**\n * Core configuration.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license   The MIT License {@link http://opensource.org/licenses/MIT}\n */\n(function () {\n    'use strict';\n\n    /* @ngInject */\n    function onConfig($urlRouterProvider, RestangularProvider, localStorageServiceProvider, SERVER_API_URL) {\n        // use \"ionic-photo-gallery\" as a localStorage name prefix so app doesn’t accidently read data from another app using the same variable names\n        localStorageServiceProvider.setPrefix('ionic-photo-gallery');\n\n        // set material design template\n        //$mdThemingProvider.theme('default')\n        //    .primaryPalette('teal')\n        //    .accentPalette('brown')\n        //    .warnPalette('deep-orange');\n\n        /*********************************************************************\n         * Route provider configuration based on these config constant values\n         *********************************************************************/\n        // set restful base API Route\n        RestangularProvider.setBaseUrl(SERVER_API_URL);\n\n        // set the `id` field to `_id`\n        RestangularProvider.setRestangularFields({\n            id: '_id'\n        });\n\n        $urlRouterProvider.otherwise('/signin');\n    }\n\n    /* @ngInject */\n    function onRun($ionicPlatform, $rootScope, $location, Authentication) {\n        $ionicPlatform.ready(function() {\n            // save user profile details to $rootScope\n            $rootScope.me = Authentication.getCurrentUser();\n\n            // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard\n            // for form inputs)\n            if (window.cordova && window.cordova.plugins.Keyboard) {\n                cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);\n            }\n            if (window.StatusBar) {\n                // org.apache.cordova.statusbar required\n                StatusBar.styleDefault();\n            }\n\n            $rootScope.$on('$stateChangeStart', function (event, toState) {\n                if(toState.data.authenticate && !Authentication.isAuthenticated()) {\n                    console.log('No authorized!');\n                    event.preventDefault();\n                    $location.path('/#/signin');\n                }\n            });\n        });\n    }\n\n    angular\n        .module('app.core')\n        .config(onConfig)\n        .run(onRun)\n        .constant('SERVER_API_URL', 'http://127.0.0.1:3000'); //192.168.0.100 - 172.20.10.3\n})();\n"
  },
  {
    "path": "ionic/www/js/core/core.js",
    "content": "/**\n * Core module.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license   The MIT License {@link http://opensource.org/licenses/MIT}\n */\n(function () {\n    'use strict';\n\n    /**\n     * @ngdoc module\n     * @name app.core\n     */\n    angular.module('app.core', []);\n\n})();\n"
  },
  {
    "path": "ionic/www/js/core/services/authentication/authentication.service.js",
    "content": "/**\n * Authentication service.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license   The MIT License {@link http://opensource.org/licenses/MIT}\n */\n(function () {\n    'use strict';\n\n    /**\n     * @ngInject\n     */\n    function AuthenticationProvider() {\n        this.$get = function($http, Restangular, Token, localStorageService) {\n            var currentUser = null;\n            function saveUserAndToken(token) {\n                // store token to local storage\n                Token.set(token);\n                // decode user data from payload token\n                currentUser = Token.decodeToken(token);\n                // save user to locale storage\n                localStorageService.set('user', currentUser);\n            }\n\n            return {\n                signup: function(params) {\n                    return Restangular\n                        .all('auth/signup')\n                        .post(params)\n                        .then(function(response) {\n                            saveUserAndToken(response.token);\n                        });\n                },\n                signin: function(params) {\n                    return Restangular\n                        .all('auth/signin')\n                        .post(params)\n                        .then(function(response) {\n                            saveUserAndToken(response.token);\n                        });\n                },\n                signout: function() {\n                    return Restangular\n                        .one('auth/signout')\n                        .get()\n                        .then(function(){\n                            currentUser = null;\n                            Token.remove();\n                        });\n                },\n                isAuthenticated: function() {\n                    return !!Token.get();\n                },\n                getCurrentUser: function() {\n                    return currentUser || localStorageService.get('user')\n                }\n            };\n        };\n    }\n\n    angular\n        .module('app.core')\n        .provider('Authentication', AuthenticationProvider);\n})();\n"
  },
  {
    "path": "ionic/www/js/core/services/authentication/base64.service.js",
    "content": "/**\n * Base64 service.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license   The MIT License {@link http://opensource.org/licenses/MIT}\n */\n(function () {\n    'use strict';\n\n    /**\n     * @ngdoc service\n     * @name Base64\n     * @module app.core\n     *\n     * @description\n     * The `Base64` service decode binary data.\n     *\n     * @ngInject\n     */\n    function Base64() {\n        return {\n            // this is used to parse the user profile\n            decode: function(str) {\n                var output = str.replace('-', '+').replace('_', '/');\n                switch (output.length % 4) {\n                    case 0:\n                        break;\n                    case 2:\n                        output += '==';\n                        break;\n                    case 3:\n                        output += '=';\n                        break;\n                    default:\n                        throw 'Illegal base64url string!';\n                }\n                // base-64: atob decodes, btoa encodes\n                return window.atob(output); // polyfill https://github.com/davidchambers/Base64.js\n            }\n        };\n\n    }\n\n    angular\n        .module('app.core')\n        .factory('Base64', Base64);\n\n})();\n"
  },
  {
    "path": "ionic/www/js/core/services/authentication/interceptor.service.js",
    "content": "/**\n * Authentication interceptor.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license   The MIT License {@link http://opensource.org/licenses/MIT}\n */\n(function () {\n    'use strict';\n\n    /**\n     * @description\n     * The http interceptor that listens for authentication failures.\n     *\n     * @param $q\n     * @param $location\n     * @param Token\n     * @returns {{request: request, responseError: responseError}}\n     * @constructor\n     * @ngInject\n     */\n    function AuthenticationInterceptor($q, $location, Token) {\n        return {\n            request: function (config) {\n                var token = Token.get();\n                if (token) {\n                    config.headers = config.headers || {};\n                    config.headers.Authorization = 'Bearer ' + token;\n                }\n                return config;\n            },\n            responseError: function (rejection) {\n                // revoke client authentication if 401 is received\n                if (rejection != null && rejection.status === 401 && !!Token.get()) {\n                    Token.remove();\n                    $location.path('/');\n                }\n                return $q.reject(rejection);\n            }\n        };\n    }\n\n    angular\n        .module('app.core')\n        .factory('AuthenticationInterceptor', AuthenticationInterceptor)\n        .config(function ($httpProvider) {\n            // we have to add the interceptor to the queue as a string because the interceptor depends upon service instances that are not available in the config block.\n            $httpProvider.interceptors.push('AuthenticationInterceptor');\n        });\n})();\n"
  },
  {
    "path": "ionic/www/js/core/services/authentication/token.service.js",
    "content": "/**\n * Token service.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license   The MIT License {@link http://opensource.org/licenses/MIT}\n */\n(function () {\n    'use strict';\n\n    /**\n     * @ngdoc service\n     * @name Token\n     * @module app.core\n     * @requires localStorageService\n     * @requires Base64\n     *\n     * @description\n     * The `Token` service store token to local storage, cookie or memory.\n     *\n     * @ngInject\n     */\n    function Token(localStorageService, Base64) {\n        /**\n         * @type {string}\n         * @private\n         */\n        var _tokenStorageKey = 'token';\n        /**\n         * @type {string}\n         * @private\n         */\n        var _cachedToken = '';\n\n        /**\n         * @ngdoc method\n         * @name Token#set\n         * @description Set token.\n         * @param {string} token\n         */\n        var set = function(token) {\n            _cachedToken = token;\n            localStorageService.set(_tokenStorageKey, token)\n        };\n        /**\n         * @ngdoc method\n         * @name Token#get\n         * @description Get token.\n         * @returns {string} token\n         */\n        var get = function() {\n            if (!_cachedToken) {\n                _cachedToken = localStorageService.get(_tokenStorageKey);\n            }\n            return _cachedToken;\n        };\n        /**\n         * @ngdoc method\n         * @name Token#remove\n         * @description Remove token.\n         */\n        var remove = function() {\n            _cachedToken = null;\n            localStorageService.remove(_tokenStorageKey);\n        };\n        /**\n         * @ngdoc method\n         * @name Token#decodeToken\n         * @description Decode the token.\n         */\n        var decodeToken = function(token) {\n            var parts = token.split('.');\n\n            if (parts.length !== 3) {\n                throw new Error('JWT must have 3 parts');\n            }\n\n            // get payload part of token that contains user data (Token look like xxxxxxxxxxx.yyyy.zzzzzzzzzzzz the y is the encoded payload.)\n            var encoded = parts[1];\n\n            // decode user data from payload token\n            var decoded = Base64.decode(encoded);\n            if (!decoded) {\n                throw new Error('Cannot decode the token');\n            }\n\n            return JSON.parse(decoded);\n        };\n\n        return {\n            set: set,\n            get: get,\n            remove: remove,\n            decodeToken: decodeToken\n        }\n    }\n\n    angular\n        .module('app.core')\n        .factory('Token', Token);\n})();\n"
  },
  {
    "path": "ionic/www/js/core/services/camera/camera.service.js",
    "content": "/**\n * Camera service that takes and uploads image to server.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license   The MIT License {@link http://opensource.org/licenses/MIT}\n */\n(function () {\n    'use strict';\n\n    /**\n     * @ngdoc service\n     * @name CameraService\n     * @module app.core\n     * @requires $q\n     * @requires $rootScope\n     * @requires $cordovaFileTransfer\n     * @requires $cordovaCamera\n     * @requires $ionicLoading\n     * @requires Token\n     * @requires SERVER_API_URL\n     * @description\n     * Service to take picture via camera phone and upload photo to server.\n     *\n     * @ngInject\n     */\n    function CameraService($q, $rootScope, $cordovaFileTransfer, $cordovaCamera, $ionicLoading, Token, SERVER_API_URL) {\n        /**\n         * @type {object}\n         * @private\n         */\n        var _cameraOptions = null;\n        // catch error when we are testing on desktop as Camera is not available on desktop\n        try {\n            _cameraOptions = {\n                quality: 75,\n                destinationType: Camera.DestinationType.FILE_URI,\n                sourceType: Camera.PictureSourceType.CAMERA,\n                allowEdit: true,\n                encodingType: Camera.EncodingType.JPEG,\n                popoverOptions: CameraPopoverOptions,\n                targetWidth: 100,\n                targetHeight: 100,\n                saveToPhotoAlbum: false\n            };\n        } catch(err) {\n            console.error('CameraService: ' + err);\n        }\n\n        /**\n         * @type {object}\n         * @private\n         */\n        var fileTransferOptions = {\n            fileKey: 'image',\n            fileName: 'img/ionic.png',\n            mimeType: 'image/png',\n            chunkedMode: false,\n            params: { // these options.params, will be available in req.body at the server-side\n                userId: $rootScope.me._id,\n                url: SERVER_API_URL\n            },\n            headers: {\n                Authorization: 'Bearer ' + Token.get()\n            }\n        };\n\n        /**\n         * @ngdoc method\n         * @name CameraService#clearCache\n         * @description Clear camera cache (only required for FILE_URI).\n         * @private\n         */\n        var clearCache = function() {\n            $cordovaCamera.cleanup().then(function () {\n                console.log('Camera cleanup success.');\n            }, function(error) {\n                console.error('Camera cleanup failed because: ' + error);\n            });\n        };\n\n        /**\n         * @ngdoc method\n         * @name CameraService#takePicture\n         * @description Take and upload picture to server.\n         */\n        var takePicture = function() {\n            var q = $q.defer();\n\n            function onSuccess(imageURI) {\n                $ionicLoading.show({template: 'Uploading...'});\n\n                // upload image to server\n                $cordovaFileTransfer.upload(SERVER_API_URL + '/images', imageURI, fileTransferOptions)\n                    .then(function() {\n                        console.log('Image has been uploaded successfully: ' + fileTransferOptions.fileName);\n                        q.resolve();\n                    }, function(error) {\n                        console.error('Image has not been uploaded successfully: ' + JSON.stringify(error));\n                        q.reject(error);\n                    }).then(function() {\n                        $ionicLoading.hide();\n                        clearCache();\n                    });\n            }\n\n            function onFailure(error) {\n                console.error(error);\n                q.reject(error);\n            }\n\n            $cordovaCamera.getPicture(_cameraOptions).then(onSuccess, onFailure);\n\n            return q.promise;\n        };\n\n        return {\n            takePicture: takePicture\n        }\n    }\n\n    angular\n        .module('app.core')\n        .factory('CameraService', CameraService);\n\n})();\n"
  },
  {
    "path": "ionic/www/js/core/services/error/error.service.js",
    "content": "/**\n * Authentication interceptor.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license   The MIT License {@link http://opensource.org/licenses/MIT}\n */\n(function () {\n    'use strict';\n\n    /**\n     * @description\n     * The http interceptor that listens for errors.\n     *\n     * @param $q\n     * @param $cordovaDialogs\n     * @returns {{responseError: responseError}}\n     * @constructor\n     * @ngInject\n     */\n    function ErrorInterceptor($q, $cordovaDialogs) {\n        return {\n            responseError: function (rejection) {\n                if(rejection != null && rejection.status === 401) {\n                    $cordovaDialogs.alert('The username or password you entered is incorrect.', 'Disconnected', 'OK');\n                } else if(rejection != null && rejection.status === 0) {\n                    $cordovaDialogs.alert('Couldn\\'t connect to the internet please check your network connection.', 'Network Error', 'OK');\n                } else if(rejection != null ) {\n                    $cordovaDialogs.alert('An error occurred on the system, please contact the system administrator.', 'Error', 'OK');\n                }\n                return $q.reject(rejection);\n            }\n        };\n    }\n\n    angular\n        .module('app.core')\n        .factory('ErrorInterceptor', ErrorInterceptor)\n        .config(function ($httpProvider) {\n            // we have to add the interceptor to the queue as a string because the interceptor depends upon service instances that are not available in the config block.\n            $httpProvider.interceptors.push('ErrorInterceptor');\n        });\n})();\n"
  },
  {
    "path": "ionic/www/js/core/services/image/image.service.js",
    "content": "/**\n * Image service.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license   The MIT License {@link http://opensource.org/licenses/MIT}\n */\n(function () {\n    'use strict';\n\n    /**\n     * @ngdoc service\n     * @name ImageService\n     * @module app.core\n     * @requires Restangular\n     * @description\n     * Service to get the image data.\n     *\n     * @ngInject\n     */\n    function ImageService(Restangular) {\n        return {\n            /**\n             * @ngdoc method\n             * @name ImageService:get\n             * @description\n             * Retrieve image by id.\n             *\n             * @returns {promise} A promise which is resolved in image data.\n             */\n            get: function(id) {\n                return Restangular\n                    .one('images', id)\n                    .get();\n            },\n            /**\n             * @ngdoc method\n             * @name ImageService:getByUser\n             * @description\n             * Retrieve all images that belong to user.\n             *\n             * @returns {promise} A promise which is resolved in image list data.\n             */\n            getByUser: function(userId) {\n                return Restangular\n                    .all('images')\n                    .getList({userId: userId});\n            },\n            /**\n             * @ngdoc method\n             * @name ImageService:delete\n             * @description\n             * Delete image by id.\n             *\n             * @returns {promise} A promise which is resolved in image list data.\n             */\n            delete: function(id) {\n                return Restangular\n                    .one('images', id)\n                    .remove();\n            }\n        };\n    }\n\n    angular\n        .module('app.core')\n        .factory('ImageService', ImageService);\n\n})();\n"
  },
  {
    "path": "ionic/www/js/core/services/user/user.service.js",
    "content": "/**\n * User service.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license   The MIT License {@link http://opensource.org/licenses/MIT}\n */\n(function () {\n    'use strict';\n\n    /**\n     * @ngdoc service\n     * @name UserService\n     * @module app.core\n     * @requires Restangular\n     * @description\n     * Service to get the user data.\n     *\n     * @ngInject\n     */\n    function UserService(Restangular) {\n        return {\n            /**\n             * @ngdoc method\n             * @name UserService:get\n             * @description\n             * Retrieve user by id.\n             *\n             * @returns {promise} A promise which is resolved in user data.\n             */\n            get: function(id) {\n                return Restangular\n                    .one('users', id)\n                    .get();\n            },\n            /**\n             * @ngdoc method\n             * @name UserService:getList\n             * @description\n             * Retrieve all users.\n             *\n             * @returns {promise} A promise which is resolved in users list data.\n             */\n            getList: function() {\n                return Restangular\n                    .all('users')\n                    .getList();\n            }\n        };\n    }\n\n    angular\n        .module('app.core')\n        .factory('UserService', UserService);\n\n})();\n"
  },
  {
    "path": "ionic/www/js/routes/galleries/galleries.controller.js",
    "content": "/**\n * Galleries controller.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license   The MIT License {@link http://opensource.org/licenses/MIT}\n */\n(function () {\n    'use strict';\n\n    /**\n     * @ngdoc controller\n     * @name GalleriesCtrl\n     * @module app.galleries\n     * @description\n     * Controller for the galleries page.\n     *\n     * @ngInject\n     */\n    function GalleriesCtrl(users) {\n        var vm = this;\n        vm.users = users;\n\n    }\n\n    angular\n        .module('app.galleries')\n        .controller('GalleriesCtrl', GalleriesCtrl);\n})();\n"
  },
  {
    "path": "ionic/www/js/routes/galleries/galleries.html",
    "content": "<ion-view view-title=\"Galleries\">\n    <ion-content class=\"galleries-view \">\n        <div class=\"row galleries-list\">\n            <div ng-repeat=\"user in vm.users\" class=\"col col-50\">\n                <a ui-sref=\"app.gallery({ userId: user._id })\" class=\"gallery\">\n                    <img class=\"gallery-image\" ng-src=\"{{::user.avatar}}\" />\n                    <div class=\"gallery-bg\"></div>\n                    <div class=\"gallery-title\">\n                        <h5>{{::user.name}}</h5>\n                        <!--<h6>8 image(s)</h6>-->\n                    </div>\n                </a>\n            </div>\n        </div>\n    </ion-content>\n</ion-view>\n\n\n\n"
  },
  {
    "path": "ionic/www/js/routes/galleries/galleries.js",
    "content": "/**\n * Galleries module.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license   The MIT License {@link http://opensource.org/licenses/MIT}\n */\n(function () {\n    'use strict';\n\n    /**\n     * @ngdoc module\n     * @name app.galleries\n     */\n    angular.module('app.galleries', []);\n\n})();\n"
  },
  {
    "path": "ionic/www/js/routes/galleries/galleries.route.js",
    "content": "/**\n * Galleries route.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license   The MIT License {@link http://opensource.org/licenses/MIT}\n */\n(function () {\n    'use strict';\n\n    /**\n     * @ngdoc object\n     * @name galleriesRoute\n     * @module app.galleries\n     * @requires $stateProvider\n     * @description\n     * Router for the galleries page.\n     *\n     * @ngInject\n     */\n    function galleriesRoute($stateProvider) {\n        $stateProvider\n            .state('app.galleries', {\n                url: '/galleries',\n                views: {\n                    'menuContent': {\n                        templateUrl: 'js/routes/galleries/galleries.html',\n                        controller: 'GalleriesCtrl as vm'\n                    }\n                },\n                resolve: {/* @ngInject */\n                    users: function(UserService){\n                        return UserService.getList();\n                    }\n                },\n                data: {\n                    authenticate: true\n                }\n            });\n    }\n\n    angular\n        .module('app.galleries')\n        .config(galleriesRoute);\n\n})();\n"
  },
  {
    "path": "ionic/www/js/routes/gallery/gallery.controller.js",
    "content": "/**\n * Gallery controller.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license   The MIT License {@link http://opensource.org/licenses/MIT}\n */\n(function () {\n    'use strict';\n\n    /**\n     * @ngdoc controller\n     * @name GalleryCtrl\n     * @module app.gallery\n     * @requires $scope\n     * @requires $stateParams\n     * @requires images\n     * @requires CameraService\n     * @requires ImageService\n     * @requires $ionicLoading\n     * @description\n     * Controller for the gallery page.\n     *\n     * @ngInject\n     */\n    function GalleryCtrl($rootScope, $scope, $stateParams, images, CameraService, ImageService, $ionicLoading) {\n        var vm = this;\n        vm.listCanSwipe = true;\n        vm.images = images;\n        vm.title = $rootScope.me._id === $stateParams.userId ? 'My Gallery' : 'Gallery';\n        vm.isCameraAvailable = $rootScope.me._id === $stateParams.userId;\n        vm.takePhoto = function() {\n            CameraService.takePicture().then(function() {\n                ImageService.getByUser($stateParams.userId).then(function(images) {\n                    vm.images = images;\n                }, function(error) {\n                    console.error('Can not load images '+ error);\n                }).then(function() {\n                    // Stop the ion-refresher from spinning\n                    $scope.$broadcast('scroll.refreshComplete');\n                });\n            });\n        };\n        vm.delete = function(image) {\n            $ionicLoading.show({template: 'Deleting...'});\n            ImageService.delete(image._id).then(function(){\n                vm.images.splice(vm.images.indexOf(image), 1);\n            }, function(error) {\n                console.error('Can not delete image '+ error);\n            }).then(function() {\n                $ionicLoading.hide();\n            });\n        };\n        vm.doRefresh = function() {\n            ImageService.getByUser($stateParams.userId).then(function(images) {\n                vm.images = images;\n            }, function(error) {\n                console.error('Can not load images '+ error);\n            }).then(function() {\n                // Stop the ion-refresher from spinning\n                $scope.$broadcast('scroll.refreshComplete');\n            });\n\n        }\n    }\n\n    angular\n        .module('app.gallery')\n        .controller('GalleryCtrl', GalleryCtrl);\n})();\n"
  },
  {
    "path": "ionic/www/js/routes/gallery/gallery.html",
    "content": "<ion-view view-title=\"{{::vm.title}}\">\n    <ion-content class=\"gallery-view\">\n        <ion-refresher pulling-text=\"Pull to refresh...\" on-refresh=\"vm.doRefresh()\"></ion-refresher>\n        <div class=\"row\" style=\"margin-top: 100px; text-align: center\" ng-if=\"vm.images.length === undefined || vm.images.length <= 0\">\n            <div class=\"col\">\n                No images...\n            </div>\n        </div>\n\n        <div class=\"row galleries-list\" ng-if=\"vm.images.length > 0\">\n            <div ng-repeat=\"image in vm.images\" class=\"col col-50\">\n                <span class=\"gallery\">\n                    <img class=\"gallery-image\" ng-src=\"{{image.url}}\"/>\n                    <div class=\"gallery-bg\"></div>\n                    <span class=\"gallery-title\" ng-click=\"vm.delete(image)\">\n                        <i class=\"ionic ion-ios-trash-outline\"></i>\n                    </span>\n                </span>\n            </div>\n        </div>\n    </ion-content>\n    <button ng-if=\"vm.isCameraAvailable\" class=\"md-fab md-primary md-fab-bottom-right camera md-button md-default-theme\" aria-label=\"Take a picture\" ng-click=\"vm.takePhoto()\">\n        <i class=\"icon ion-camera\"></i>\n    </button>\n    <!--<md-button class=\"md-fab md-primary md-fab-bottom-right camera\" aria-label=\"Take a picture\" ng-click=\"vm.takePhoto()\">-->\n    <!--<i class=\"icon ion-camera\"></i>-->\n    <!--</md-button>-->\n</ion-view>\n\n\n"
  },
  {
    "path": "ionic/www/js/routes/gallery/gallery.js",
    "content": "/**\n * Gallery module.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license   The MIT License {@link http://opensource.org/licenses/MIT}\n */\n(function () {\n    'use strict';\n\n    /**\n     * @ngdoc module\n     * @name app.gallery\n     */\n    angular.module('app.gallery', []);\n\n})();\n"
  },
  {
    "path": "ionic/www/js/routes/gallery/gallery.route.js",
    "content": "/**\n * Gallery route.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license   The MIT License {@link http://opensource.org/licenses/MIT}\n */\n(function () {\n    'use strict';\n\n    /**\n     * @ngdoc object\n     * @name galleryRoute\n     * @module app.gallery\n     * @requires $stateProvider\n     * @description\n     * Router for the gallery page.\n     *\n     * @ngInject\n     */\n    function galleryRoute($stateProvider) {\n        $stateProvider\n            .state('app.gallery', {\n                url: '/galleries/:userId',\n                views: {\n                    'menuContent': {\n                        templateUrl: 'js/routes/gallery/gallery.html',\n                        controller: 'GalleryCtrl as vm'\n                    }\n                },\n                resolve: {/* @ngInject */\n                    images: function(ImageService, $stateParams){\n                        var userId = $stateParams.userId;\n                        return ImageService.getByUser(userId);\n                    }\n                },\n                data: {\n                    authenticate: true\n                }\n            });\n    }\n\n    angular\n        .module('app.gallery')\n        .config(galleryRoute);\n\n})();\n"
  },
  {
    "path": "ionic/www/js/routes/layout/layout.controller.js",
    "content": "/**\n * Layout controller.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license   The MIT License {@link http://opensource.org/licenses/MIT}\n */\n(function () {\n    'use strict';\n\n    /**\n     * @ngdoc controller\n     * @name LayoutCtrl\n     * @module app.gallery\n     * @requires $state\n     * @requires Authentication\n     * @description\n     * Controller for the layout page.\n     *\n     * @ngInject\n     */\n    function LayoutCtrl($state, Authentication) {\n        var vm = this;\n\n        vm.signOut = function() {\n            Authentication.signout().then(function () {\n                $state.go('signin');\n            }, function (err) {\n                console.log('error ' + err);\n                $state.go('signin');\n            });\n        };\n\n        vm.launchMartinMicundaPage = function(){\n            window.open(\"http://martinmicunda.com\", \"_blank\", \"closebuttoncaption=Done,location=no\");\n        };\n    }\n\n    angular\n        .module('app.layout')\n        .controller('LayoutCtrl', LayoutCtrl);\n})();\n"
  },
  {
    "path": "ionic/www/js/routes/layout/layout.js",
    "content": "/**\n * Layout module.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license   The MIT License {@link http://opensource.org/licenses/MIT}\n */\n(function () {\n    'use strict';\n\n    /**\n     * @ngdoc module\n     * @name app.layout\n     */\n    angular.module('app.layout', []);\n\n})();\n"
  },
  {
    "path": "ionic/www/js/routes/layout/layout.route.js",
    "content": "/**\n * Layout route.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license   The MIT License {@link http://opensource.org/licenses/MIT}\n */\n(function () {\n    'use strict';\n\n    /**\n     * @ngdoc object\n     * @name layoutRoute\n     * @module app.layout\n     * @requires $stateProvider\n     * @description\n     * Router for the layout page.\n     *\n     * @ngInject\n     */\n    function layoutRoute($stateProvider) {\n        $stateProvider\n            .state('app', {\n                url: '',\n                abstract: true,\n                templateUrl: 'js/routes/layout/side-menu.html',\n                controller: 'LayoutCtrl as vm'\n            })\n    }\n\n    angular\n        .module('app.layout')\n        .config(layoutRoute);\n\n})();\n"
  },
  {
    "path": "ionic/www/js/routes/layout/side-menu.html",
    "content": "<ion-side-menus enable-menu-with-back-views=\"false\">\n    <ion-side-menu-content>\n        <ion-nav-bar class=\"bar-positive\">\n            <ion-nav-back-button>\n            </ion-nav-back-button>\n\n            <ion-nav-buttons side=\"left\">\n                <!--<md-button class=\"md-icon-button\" aria-label=\"Menu\" menu-toggle=\"left\">-->\n                    <!--<md-icon md-svg-icon=\"img/icons/menu.svg\"></md-icon>-->\n                <!--</md-button>-->\n                <button class=\"button button-icon button-clear ion-navicon\" menu-toggle=\"left\">\n                </button>\n            </ion-nav-buttons>\n        </ion-nav-bar>\n        <ion-nav-view name=\"menuContent\"></ion-nav-view>\n    </ion-side-menu-content>\n\n    <ion-side-menu side=\"left\" expose-aside-when=\"large\">\n        <ion-content scroll=\"false\" class=\"sidebar\">\n            <div class=\"heading-item item item-avatar\">\n                <img ng-src=\"{{$root.me.avatar}}\" alt=\"avatar\" >\n                <h2 class=\"greeting\">{{$root.me.name}}</h2>\n                <p class=\"message\">Welcome back</p>\n            </div>\n            <ul class=\"nav sidebar-nav\">\n                <li md-ink-ripple=\"#333\">\n                    <a ui-sref=\"app.gallery({ userId: $root.me._id })\" nav-clear menu-close>\n                        <i class=\"sidebar-icon icon ion-image\"></i>\n                        My Gallery\n                    </a>\n                </li>\n                <li md-ink-ripple=\"#333\">\n                    <a ui-sref=\"app.galleries\" nav-clear menu-close>\n                        <i class=\"sidebar-icon icon ion-images\"></i>\n                        Galleries\n                    </a>\n                </li>\n                <li md-ink-ripple=\"#333\">\n                    <a ui-sref=\"app.user({ userId: $root.me._id })\" nav-clear menu-close>\n                        <i class=\"sidebar-icon icon ion-person\"></i>\n                        My Profile\n                    </a>\n                </li>\n                <li md-ink-ripple=\"#333\">\n                    <a ui-sref=\"app.users\" nav-clear menu-close>\n                        <i class=\"sidebar-icon icon ion-person-stalker\"></i>\n                        Users\n                    </a>\n                </li>\n                <li class=\"divider\"></li>\n                <li md-ink-ripple=\"#333\">\n                    <a ng-click=\"vm.signOut()\">\n                        <i class=\"sidebar-icon icon ion-log-out\"></i>\n                        Sign Out\n                    </a>\n                </li>\n            </ul>\n        </ion-content>\n    </ion-side-menu>\n</ion-side-menus>\n\n"
  },
  {
    "path": "ionic/www/js/routes/signin/signin.controller.js",
    "content": "/**\n * Signin controller.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license   The MIT License {@link http://opensource.org/licenses/MIT}\n */\n(function () {\n    'use strict';\n\n    /**\n     * @ngdoc controller\n     * @name SigninCtrl\n     * @module app.signin\n     * @requires $rootScope\n     * @requires $state\n     * @requires Authentication\n     * @requires $cordovaVibration\n     * @description\n     * Controller for the signin page.\n     *\n     * @ngInject\n     */\n    function SigninCtrl($rootScope, $state, Authentication, $cordovaVibration) {\n        var vm = this;\n        vm.signIn = function(credentials, isValid) {\n            if(!isValid) {return;}\n            Authentication.signin(credentials).then(function () {\n                // save user profile details to $rootScope\n                $rootScope.me = Authentication.getCurrentUser();\n\n                $state.go('app.gallery', { userId: $rootScope.me._id});\n            }, function(error) {\n                $cordovaVibration.vibrate(100);\n                console.log('error ' + error);\n            });\n        };\n        vm.goToSignup = function(){\n            $state.go('signup');\n        };\n    }\n\n    angular\n        .module('app.signin')\n        .controller('SigninCtrl', SigninCtrl);\n})();\n"
  },
  {
    "path": "ionic/www/js/routes/signin/signin.html",
    "content": "<ion-view class=\"signin-header\">\n    <ion-content scroll=\"false\" layout=\"column\" class=\"has-header\">\n        <div class=\"text-center\">\n            <span class=\"circle\">MM</span>\n            <p class=\"pb-50\">\n                By <a class=\"pseudo-link\" href=\"http://martinmicunda.com\" target=\"_blank\">@martinmicunda</a>\n            </p>\n        </div>\n        <form name=\"signinForm\" ng-submit=\"vm.signIn(credentials, signinForm.$valid)\">\n            <md-content class=\"md-padding\">\n                <md-input-container md-no-float>\n                    <md-icon md-svg-src=\"img/icons/ic_mail_24px.svg\" class=\"md-default-theme\" aria-hidden=\"true\">\n                        <i class=\"icon ion-email\"></i>\n                    </md-icon>\n                    <input ng-model=\"credentials.email\" type=\"email\" name=\"email\" placeholder=\"Email\" ng-required=\"true\">\n                    <div ng-if=\"signinForm.email.$invalid && signinForm.email.$dirty\" ng-messages=\"signinForm.email.$error\" class=\"messages\">\n                        <div ng-message=\"email\">Your email address is invalid.</div>\n                    </div>\n                </md-input-container>\n                <md-input-container md-no-float>\n                    <md-icon md-svg-src=\"img/icons/ic_lock_24px.svg\" class=\"md-default-theme\" aria-hidden=\"true\">\n                        <i class=\"icon ion-locked\"></i>\n                    </md-icon>\n                    <input ng-model=\"credentials.password\" type=\"password\" name=\"password\" placeholder=\"Password\" ng-required=\"true\">\n                    <div ng-if=\"signinForm.password.$invalid && signinForm.password.$dirty\" ng-messages=\"signinForm.password.$error\" class=\"messages\">\n                        <div ng-message=\"required\">This field is required.</div>\n                    </div>\n                </md-input-container>\n            </md-content>\n\n            <div class=\"padding\">\n                <button type=\"submit\" class=\"button button-block md-raised md-primary md-button md-default-theme\">\n                    Sign In\n                </button>\n                <!--<md-button type=\"submit\" class=\"button button-block md-raised md-primary md-button md-default-theme\">Sign In</md-button>-->\n            </div>\n        </form>\n        <p class=\"text-center text-muted text-small\">Don't have an account yet? <button class=\"link-button\" ng-click=\"vm.goToSignup()\">Sign Up</button></p>\n    </ion-content>\n</ion-view>\n"
  },
  {
    "path": "ionic/www/js/routes/signin/signin.js",
    "content": "/**\n * Signin module.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license   The MIT License {@link http://opensource.org/licenses/MIT}\n */\n(function () {\n    'use strict';\n\n    /**\n     * @ngdoc module\n     * @name app.signin\n     */\n    angular.module('app.signin', []);\n\n})();\n"
  },
  {
    "path": "ionic/www/js/routes/signin/signin.route.js",
    "content": "/**\n * Signin route.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license   The MIT License {@link http://opensource.org/licenses/MIT}\n */\n(function () {\n    'use strict';\n\n    /**\n     * @ngdoc object\n     * @name signinRoute\n     * @module app.signin\n     * @requires $stateProvider\n     * @description\n     * Router for the signin page.\n     *\n     * @ngInject\n     */\n    function signinRoute($stateProvider) {\n        $stateProvider\n            .state('signin', {\n                url: '/signin',\n                templateUrl: 'js/routes/signin/signin.html',\n                controller: 'SigninCtrl as vm',\n                data: {\n                    authenticate: false\n                }\n            });\n    }\n\n    angular\n        .module('app.signin')\n        .config(signinRoute);\n\n})();\n"
  },
  {
    "path": "ionic/www/js/routes/signup/signup.controller.js",
    "content": "/**\n * Signup controller.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license   The MIT License {@link http://opensource.org/licenses/MIT}\n */\n(function () {\n    'use strict';\n\n    /**\n     * @ngdoc controller\n     * @name SignupCtrl\n     * @module app.signup\n     * @requires $rootScope\n     * @requires $state\n     * @requires Authentication\n     * @description\n     * Controller for the signup page.\n     *\n     * @ngInject\n     */\n    function SignupCtrl($location, $rootScope, $state, Authentication) {\n        var vm = this;\n        vm.user = {};\n        vm.signUp = function(user, isValid) {\n            if(!isValid) {return;}\n            Authentication.signup(user).then(function () {\n                // save user profile details to $rootScope\n                $rootScope.me = Authentication.getCurrentUser();\n\n                $state.go('app.gallery', { userId: $rootScope.me._id});\n            }, function(err) {\n                console.error('error' + err);\n            });\n        };\n\n        vm.goHome = function() {\n            $location.path('/');\n        };\n\n        vm.goToSignin = function(){\n            $state.go('signin');\n        };\n    }\n\n    angular\n        .module('app.signup')\n        .controller('SignupCtrl', SignupCtrl);\n})();\n"
  },
  {
    "path": "ionic/www/js/routes/signup/signup.html",
    "content": "<ion-view class=\"signup-header\">\n    <ion-content scroll=\"false\" layout=\"column\" class=\"has-header md-padding\">\n        <form name=\"signupForm\" ng-submit=\"vm.signUp(user, signupForm.$valid)\">\n            <md-content>\n                <md-input-container md-no-float>\n                    <md-icon md-svg-src=\"img/icons/ic_person_24px.svg\" class=\"md-default-theme\" aria-hidden=\"true\">\n                        <i class=\"icon ion-person\"></i>\n                    </md-icon>\n                    <input type=\"text\" placeholder=\"Name\" name=\"name\" ng-model=\"user.name\" ng-required=\"true\">\n                    <div ng-if=\"signupForm.name.$invalid && signupForm.name.$dirty\" ng-messages=\"signupForm.name.$error\" class=\"messages\">\n                        <div ng-message=\"required\">This field is required.</div>\n                    </div>\n                </md-input-container>\n\n                <md-input-container md-no-float>\n                    <md-icon md-svg-src=\"img/icons/ic_mail_24px.svg\" class=\"md-default-theme\" aria-hidden=\"true\">\n                        <i class=\"icon ion-email\"></i>\n                    </md-icon>\n                    <input type=\"email\" placeholder=\"Email\" name=\"email\" ng-model=\"user.email\" ng-required=\"true\">\n                    <div ng-if=\"signupForm.email.$invalid && signupForm.email.$dirty\" ng-messages=\"signupForm.email.$error\" class=\"messages\">\n                        <div ng-message=\"email\">Your email address is invalid.</div>\n                    </div>\n                </md-input-container>\n\n                <md-input-container md-no-float>\n                    <md-icon md-svg-src=\"img/icons/ic_lock_24px.svg\" class=\"md-default-theme\" aria-hidden=\"true\">\n                        <i class=\"icon ion-locked\"></i>\n                    </md-icon>\n                    <input type=\"password\" placeholder=\"Password\" name=\"password\" ng-model=\"user.password\" ng-minlength=\"6\" ng-required=\"true\">\n                    <div ng-if=\"signupForm.password.$invalid && signupForm.password.$dirty\" ng-messages=\"signupForm.password.$error\" class=\"messages\">\n                        <div ng-message=\"minlength\">6 characters minimum.</div>\n                    </div>\n                </md-input-container>\n            </md-content>\n            <div class=\"padding\">\n                <button type=\"submit\" class=\"button button-block md-raised md-primary md-button md-default-theme\">\n                    Sign Up\n                </button>\n                <!--<md-button type=\"submit\" class=\"button button-block md-raised md-primary md-button md-default-theme\">Sign Up</md-button>-->\n            </div>\n        </form>\n        <p class=\"text-center text-muted text-small\">Already have an account? <button class=\"link-button\" ng-click=\"vm.goToSignin()\">Sign In</button></p>\n    </ion-content>\n</ion-view>\n\n"
  },
  {
    "path": "ionic/www/js/routes/signup/signup.js",
    "content": "/**\n * Signup module.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license   The MIT License {@link http://opensource.org/licenses/MIT}\n */\n(function () {\n    'use strict';\n\n    /**\n     * @ngdoc module\n     * @name app.signup\n     */\n    angular.module('app.signup', []);\n\n})();\n"
  },
  {
    "path": "ionic/www/js/routes/signup/signup.route.js",
    "content": "/**\n * Signup route.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license   The MIT License {@link http://opensource.org/licenses/MIT}\n */\n(function () {\n    'use strict';\n\n    /**\n     * @ngdoc object\n     * @name signupRoute\n     * @module app.signup\n     * @requires $stateProvider\n     * @description\n     * Router for the signup page.\n     *\n     * @ngInject\n     */\n    function signupRoute($stateProvider) {\n        $stateProvider\n            .state('signup', {\n                url: '/signup',\n                templateUrl: 'js/routes/signup/signup.html',\n                controller: 'SignupCtrl as vm',\n                data: {\n                    authenticate: false\n                }\n            });\n    }\n\n    angular\n        .module('app.signup')\n        .config(signupRoute);\n\n})();\n"
  },
  {
    "path": "ionic/www/js/routes/user/user.controller.js",
    "content": "/**\n * User controller.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license   The MIT License {@link http://opensource.org/licenses/MIT}\n */\n(function () {\n    'use strict';\n\n    /**\n     * @ngdoc controller\n     * @name UserCtrl\n     * @module app.users\n     * @requires $rootScope\n     * @requires user\n     * @description\n     * Controller for the user page.\n     *\n     * @ngInject\n     */\n    function UserCtrl($rootScope, user) {\n        var vm = this;\n        vm.user = user;\n        vm.title = $rootScope.me._id === user._id ? 'My Profile' : 'User';\n    }\n\n    angular\n        .module('app.user')\n        .controller('UserCtrl', UserCtrl);\n})();\n"
  },
  {
    "path": "ionic/www/js/routes/user/user.html",
    "content": "<ion-view view-title=\"{{vm.title}}\" class=\"profile-view\">\n    <!--<ion-content class=\"padding\">-->\n        <!--<p> Name: {{ ::vm.user.firstName }} {{ ::vm.user.lastName }}</p>-->\n        <!--<p> Email: {{ ::vm.user.email }}</p>-->\n    <!--</ion-content>-->\n    <!--<ion-content>-->\n        <!--<div class=\"list\">-->\n            <!--<ion-item class=\"item-avatar-left item ng-binding\">-->\n                <!--<img ng-src=\"{{user.photo.thumb_link}}\">-->\n                <!--<h2>{{::vm.user.name}}</h2>-->\n                <!--<p>{{::vm.user.email }}</p>-->\n            <!--</ion-item>-->\n        <!--</div>-->\n    <!--</ion-content>-->\n\n    <ion-content class=\"text-center\">\n        <div class=\"item-avatar\">\n            <img ng-src=\"{{::vm.user.avatar}}\" alt=\"{{::vm.user.name}}\">\n        </div>\n        <h2>{{::vm.user.name}}</h2>\n        <p>{{::vm.user.email }}</p>\n    </ion-content>\n</ion-view>\n"
  },
  {
    "path": "ionic/www/js/routes/user/user.js",
    "content": "/**\n * Users module.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license   The MIT License {@link http://opensource.org/licenses/MIT}\n */\n(function () {\n    'use strict';\n\n    /**\n     * @ngdoc module\n     * @name app.user\n     */\n    angular.module('app.user', []);\n\n})();\n"
  },
  {
    "path": "ionic/www/js/routes/user/user.route.js",
    "content": "/**\n * User route.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license   The MIT License {@link http://opensource.org/licenses/MIT}\n */\n(function () {\n    'use strict';\n\n    /**\n     * @ngdoc object\n     * @name userRoute\n     * @module app.users\n     * @requires $stateProvider\n     * @description\n     * Router for the user page.\n     *\n     * @ngInject\n     */\n    function userRoute($stateProvider) {\n        $stateProvider\n            .state('app.user', {\n                url: '/users/:userId',\n                views: {\n                    'menuContent': {\n                        templateUrl: 'js/routes/user/user.html',\n                        controller: 'UserCtrl as vm'\n                    }\n                },\n                resolve: {/* @ngInject */\n                    user: function(UserService, $stateParams){\n                        var userId = $stateParams.userId;\n                        return UserService.get(userId);\n                    }\n                },\n                data: {\n                    authenticate: true\n                }\n            });\n    }\n\n    angular\n        .module('app.user')\n        .config(userRoute);\n\n})();\n"
  },
  {
    "path": "ionic/www/js/routes/users/users.controller.js",
    "content": "/**\n * Users controller.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license   The MIT License {@link http://opensource.org/licenses/MIT}\n */\n(function () {\n    'use strict';\n\n    /**\n     * @ngdoc controller\n     * @name UsersCtrl\n     * @module app.users\n     * @requires users\n     * @description\n     * Controller for the users page.\n     *\n     * @ngInject\n     */\n    function UsersCtrl(users) {\n        var vm = this;\n        vm.users = users;\n    }\n\n    angular\n        .module('app.users')\n        .controller('UsersCtrl', UsersCtrl);\n})();\n"
  },
  {
    "path": "ionic/www/js/routes/users/users.html",
    "content": "<ion-view view-title=\"Users\">\n    <ion-content>\n        <div ng-repeat=\"user in vm.users\" ui-sref=\"app.user({ userId: user._id })\" md-ink-ripple=\"#333\">\n            <a class=\"item item-icon-left item-icon-right item-avatar\" ng-click=\"openBrowser()\">\n                <img ng-src=\"{{user.avatar}}\" alt=\"{{user.name}}\" />\n                <div class=\"pt-10\">\n                    <span class=\"title\">{{user.name}}</span>\n                </div>\n                <i class=\"icon ion-arrow-right-c\"></i>\n            </a>\n        </div>\n    </ion-content>\n</ion-view>\n\n"
  },
  {
    "path": "ionic/www/js/routes/users/users.js",
    "content": "/**\n * Users module.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license   The MIT License {@link http://opensource.org/licenses/MIT}\n */\n(function () {\n    'use strict';\n\n    /**\n     * @ngdoc module\n     * @name app.users\n     */\n    angular.module('app.users', []);\n\n})();\n"
  },
  {
    "path": "ionic/www/js/routes/users/users.route.js",
    "content": "/**\n * Users route.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license   The MIT License {@link http://opensource.org/licenses/MIT}\n */\n(function () {\n    'use strict';\n\n    /**\n     * @ngdoc object\n     * @name usersRoute\n     * @module app.users\n     * @requires $stateProvider\n     * @description\n     * Router for the users page.\n     *\n     * @ngInject\n     */\n    function usersRoute($stateProvider) {\n        $stateProvider\n            .state('app.users', {\n                url: '/users',\n                views: {\n                    'menuContent': {\n                        templateUrl: 'js/routes/users/users.html',\n                        controller: 'UsersCtrl as vm'\n                    }\n                },\n                resolve: {/* @ngInject */\n                    users: function(UserService){\n                        return UserService.getList();\n                    }\n                },\n                data: {\n                    authenticate: true\n                }\n            });\n    }\n\n    angular\n        .module('app.users')\n        .config(usersRoute);\n\n})();\n"
  },
  {
    "path": "server/.editorconfig",
    "content": "# http://editorconfig.org\nroot = true\n\n[*]\nindent_style = space\nindent_size = 4\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[*.md]\ntrim_trailing_whitespace = false"
  },
  {
    "path": "server/.gitignore",
    "content": "# Logs\nlogs\n*.log\n\n# Runtime data\npids\n*.pid\n*.seed\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Compiled binary addons (http://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directory\n# Commenting this out is preferred by some people, see\n# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-\nnode_modules\n\n# Users Environment Variables\n.lock-wscript\n\n# DevOps\n*.box\n.vagrant\n\n# IDEs\n.idea/\n\n.envrc\n\n.bash_history\n.cache\n.config\n.node-gyp\n.npm\n\n# uploaded files\nuploads/\n"
  },
  {
    "path": "server/.jshintignore",
    "content": "node_modules/**/*\nbuild\n"
  },
  {
    "path": "server/.jshintrc",
    "content": "{\n    \"node\": true,\n    \"esnext\": true,\n    \"bitwise\": true,\n    \"camelcase\": true,\n    \"curly\": true,\n    \"eqeqeq\": true,\n    \"immed\": true,\n    \"indent\": 2,\n    \"latedef\": true,\n    \"newcap\": true,\n    \"noarg\": true,\n    \"quotmark\": \"single\",\n    \"regexp\": true,\n    \"undef\": true,\n    \"unused\": true,\n    \"strict\": true,\n    \"trailing\": true,\n    \"smarttabs\": true,\n    \"white\": true,\n    \"maxlen\": 80,\n    \"latedef\": true\n}\n"
  },
  {
    "path": "server/LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015 Martin Micunda\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n"
  },
  {
    "path": "server/README.md",
    "content": "Ionic Photo Gallery\n======================\n \n[![Deploy](https://www.herokucdn.com/deploy/button.png)](https://heroku.com/deploy?template=https://github.com/martinmicunda/ionic-photo-gallery/tree/heroku)\n[![NPM](http://img.shields.io/npm/v/ionic-photo-gallery-server.svg?style=flat)](https://www.npmjs.org/package/ionic-photo-gallery-server)\n\nA hybrid app with authentication that allows registered users view a gallery of photos they have uploaded via the camera phone. The blog post I have written about this project can be found on my [blog](http://martinmicunda.com/2015/04/10/build-ionic-photo-gallery-app-I/). \n\n![Ionic Photo Gallery Screenshots I](https://raw.githubusercontent.com/martinmicunda/ionic-photo-gallery/master/ionic-photo-gallery-screenshots-I.jpg \"Ionic Photo Gallery Screenshots I\")\n![Ionic Photo Gallery Screenshots II](https://raw.githubusercontent.com/martinmicunda/ionic-photo-gallery/master/ionic-photo-gallery-screenshots-II.jpg \"Ionic Photo Gallery Screenshots II\")\n\n## Table of Contents\n- [Technologies Used](#technologies-used)\n- [Architecture Diagram](#architecture-diagram)\n    - [Development](#diagram-development)\n- [Installation & Configuration](#installation-and-configuration)\n    - [Platform & Tools](#platform-and-tools)\n    - [Installation](#installation)\n- [Running App](#running-app)\n    - [Server](#server) \n    - [Ionic](#ionic) \n- [Building for iOS](#building-for-ios)\n- [Building for Android](#building-for-android)\n- [Vagrant](#vagrant)\n- [Ansible](#ansible)\n- [FAQ](#faq)\n- [License](#license)\n\n## Technologies Used\n| Mobile Side | Server Side | DevOps | \n|:-------------------:|:-------------------:|:-------------------:| \n| [Angular.js](http://angularjs.org/) ![Angularjs](https://avatars1.githubusercontent.com/u/139426?s=30) | [Node.js](http://nodejs.org/) <img src=\"http://nodejs.org/images/logo-light.svg\" height=\"30\" width=\"80\" /> | [Gulp](http://gulpjs.com/) ![Gulp](https://avatars0.githubusercontent.com/u/6200624?s=30) &nbsp; [Bower](http://bower.io/) ![Bower] (https://avatars3.githubusercontent.com/u/3709251?s=30) |\n[Ionic](http://ionicframework.com/) <img src=\"http://upload.wikimedia.org/wikipedia/commons/d/d1/Ionic_Logo.svg\" height=\"45\" width=\"80\" /> | [MongoDB](http://www.mongodb.org/) ![MongoDB] (https://avatars3.githubusercontent.com/u/45120?v=2&s=30) | [NPM](https://www.npmjs.org/) ![NPM] (https://avatars0.githubusercontent.com/u/6078720?s=30) &nbsp;                 [Ansible](https://www.ansible.com/) ![Ansible] (https://avatars3.githubusercontent.com/u/1507452?v=2&s=30) | \n[Material Design](https://material.angularjs.org/) ![Angularjs](https://avatars1.githubusercontent.com/u/139426?s=30) | [Express.js](http://expressjs.com/)<img src=\"https://cldup.com/wpGXm1cWwB.png\" height=\"40\" width=\"145\"> | [Vagrant](http://www.vagrantup.com/) <img src=\"https://www.vagrantup.com/images/logo_vagrant-81478652.png\" height=\"40\" width=\"145\"> |\n| [Cordova](https://cordova.apache.org/) <img src=\"https://cordova.apache.org/images/cordova_256.png\" height=\"35\" width=\"45\" /> | [Redis](http://redis.io/) <img src=\"http://upload.wikimedia.org/wikipedia/en/thumb/6/6b/Redis_Logo.svg/467px-Redis_Logo.svg.png?v=2&s=30\" height=\"35\" width=\"125\"> | \n\n## Architecture Diagram\n### <a name=\"diagram-development\"></a>Development\n![Ionic Photo Gallery Development Architecture Diagram](https://raw.githubusercontent.com/martinmicunda/ionic-photo-gallery/master/ionic-photo-gallery.jpg \"Ionic Photo Gallery Development Architecture Diagram\")\n\n## <a name=\"installation-and-configuration\"></a> Installation & Configuration\n### <a name=\"platform-and-tools\"></a> Platform & Tools\nYou need to have installed follow tools on your machine:\n\n- [Virtualbox](https://www.virtualbox.org/wiki/Downloads) 4.3.16+\n- [Vagrant](http://www.vagrantup.com/downloads.html) 1.6.2+\n- [Ansible](http://docs.ansible.com/intro_installation.html) 1.7.0+\n\n### <a name=\"installation\"></a> Installation\n\n**1.** Clone main repository:\n```bash\n$ git clone git@github.com:martinmicunda/ionic-photo-gallery.git \n$ cd ionic-photo-gallery\n```\n\n**2.** The following command would add a new `ubuntu trusty64 box`, and if an existing one is found, it will override it:\n\n```bash\n$ vagrant box add ubuntu/trusty64 --force\n```\n>**NOTE:** This process may take a while, as most Vagrant boxes will be at least **200 MB** big.\n\nVerify that box was installed by running the `list` subcommand that will list the boxes installed within Vagrant along with the provider that backs the box:\n\n```bash\n$ vagrant box list\nubuntu/trusty64  (virtualbox, 14.04)\n```\n**3.** The following command would install an `ansible roles` for this project, and if an existing one is found, it will override it:\n\n```bash\n$ bash bin/ansible-install-roles.sh\n```\nVerify that ansible roles were installed by running the `list` subcommand that will list the installed roles:\n\n```bash\n$ ansible-galaxy list\n- DavidWittman.redis, 1.0.3\n- laggyluke.direnv, v2.6.0\n- martinmicunda.common, v1.0.1\n- martinmicunda.ionic, v1.0.0\n- martinmicunda.nodejs, v1.0.1\n- nickp666.android-sdk, v0.0.1\n- Stouts.mongodb, 2.1.8\n- williamyeh.oracle-java, master\n```\n**4.** Now, run `vagrant up` that will create and provisioning `default` VM box. \n\n```bash\n$ vagrant up\n```\n>**NOTE:** **Vagrant will provision the virtual machine only once on the first run, any subsequent provisioning must be executed with the** `--provision` **flag either** `vagrant up --provision` **or** `vagrant reload --provision` **or** `vagrant provision` **if vagrant box is already running. The provisioning will re-run also if you destroy the VM and rebuild it with** `vagrant destroy` **and** `vagrant up` **.**\n\nIf there have been no errors when executing the above commands, the machines  `default` should be created. The following command would outputs status of the vagrant machine:\n\n```bash\n$ vagrant status\nCurrent machine states:\ndefault                   running (virtualbox)\n```\nNow you should be able to ssh into box:\n```bash\n$ vagrant ssh \n```\n## Running App\n### Server\n**1.** To start the server you need to ssh into box:\n```bash\n$ vagrant ssh\n```\n**2.** Install the server dependencies:\n```bash\n$ cd server\n$ npm install\n```\n**3.** Start the server:\n```bash\n$ npm start\n```\n>**NOTE:** **The [direnv](http://direnv.net/) is use as an environment variable manager so when you first time cd into server directory with a `.envrc` file in it, it will refuse to load the file. This is to protect you, since the contents of the .envrc will be executed by your shell, and they might come from untrusted sources. Simply run `direnv allow`, and it will trust that file until the next time it changes.**\n\n### Ionic\n**1.** To start the server you need to ssh into box:\n```bash\n$ vagrant ssh\n```\n**2.** Install the ionic dependencies:\n```bash\n$ cd ionic\n$ npm install\n```\n**3.** Start the ionic:\n```bash\n$ npm start\n```\nOpen up your browser and navigate to [http://127.0.0.1:8100](http://127.0.0.1:8100) and you should see ionic app up and running.\n\n## <a name=\"building-for-ios\"></a> Building for iOS\n**1.** ssh into box:\n```bash\n$ vagrant ssh\n```\n**2.** Add support for the iOS platform:\n```bash\n$ cd ionic\n$ ionic platform add ios\n```\n**3.** Build the project:\n```bash\n$ ionic build ios\n```\n**4.** Open `ionic-photo-gallery.xcodeproj` in the `ionic-photo-gallery/ionic/platforms/ios` folder.\n\n**5.** In [Xcode](https://developer.apple.com/xcode/), run the application on a device connected to your computer or in the iOS emulator.\n\n## <a name=\"building-for-android\"></a> Building for Android\n**1.** ssh into box:\n```bash\n$ vagrant ssh\n```\n**2.** Add support for the Android platform:\n```bash\n$ cd ionic\n$ ionic platform add android\n```\n**3.** Build the project:\n```bash\n$ ionic build android\n```\n\n**NOTE:** (martin) work in progress!!\n\n1. Start Genymotion\n2. Open Genymotion Shell\n3. Run follow command to get IP address\n\n```bash\n$ devices list\n```\nyou should see something like this:\n\n```bash\nGenymotion virtual device 0 is off. Please select a new virtual device with command : devices select\nAvailable devices:\n\n Id | Select |    Status     |   Type   |   IP Address    |      Name\n----+--------+---------------+----------+-----------------+---------------\n  0 |        |            On |  virtual |  192.168.58.101 | Samsung Galaxy S4 - 4.4.4 - API 19 - 1080x1920\n```\n5. Go to vagrant box using 'vagrant up' and 'vagrant ssh'.\n6. Type: `adb connect 192.168.56.101` and `adb devices`. You should see something like this:\n```\nvagrant@vagrant-ubuntu-trusty-64:~$ adb connect 192.168.58.101\nconnected to 192.168.58.101:5555\nvagrant@vagrant-ubuntu-trusty-64:~$ adb devices\nList of devices attached\n192.168.58.101:5555     device\n```\n7. Run `ionic run android`\n\n## Vagrant \nThere’s a ton of commands you can use to talk to Vagrant. For a full list see the [official docs](http://docs.vagrantup.com/v2/cli/), but here are the more common ones.\n\n* `vagrant up` - use this command to `start` your virtual environment\n* `vagrant halt` - use this command to `stop` your virtual environment\n* `vagrant suspend` - use this command to `pause` your virtual environment, make sure you do this before shutting down your computer to safely be able to restore the environment later.\n* `vagrant destroy` - use this command to `removes` your virtual environment from your machine\n* `vagrant reload` - use this command to your virtual environment, if you add the `--provision` flag, it will reprovision the box as well; this is useful with removing or adding things to the server via Ansible.\n* `vagrant ssh` - use this command to `connect` to the virtual server\n\n## Ansible\nTo get better understanding how Ansible works check the [official docs](http://docs.ansible.com/). Ansible installs the following software:\n\n* [git](http://git-scm.com/)\n* [node.js](https://nodejs.org/)\n* [npm](https://www.npmjs.com/)\n* [mongodb](https://www.mongodb.org/)\n* [redis](http://redis.io/)\n* [java 7](http://www.oracle.com/technetwork/java/javase/downloads/jre7-downloads-1880261.html)\n* [android SDK](https://developer.android.com/sdk/index.html)\n* [apache ant](http://ant.apache.org/)\n* [cordova](https://cordova.apache.org/)\n* [ionic CLI](http://ionicframework.com/docs/cli/)\n* [direnv](http://direnv.net/)\n\nThe `mongodb` and `redis` services are started after provisioning takes place.\n\n## FAQ\n### What if I want to uninstall application?\n**1.** The following command would permanently removes the `default` virtual box from your machine:\n```bash\n$ vagrant destroy\n```\n**2.** The following command would uninstall an `ansible roles` for this project:\n```bash\n$ bash bin/ansible-uninstall-roles.sh\n```\n\n**4.** The following command would remove  `trusty64 box`:\n```bash\n$ vagrant box remove trusty64\n```\n### What if I want a fresh install?\nIf you wish to destroy the `default` virtual boxe to make sure you have a fresh start, you can do these steps:\n```bash\n $ vagrant destroy \n $ vagrant up\n```\n\n## License\n\n    The MIT License\n    \n    Copyright (c) 2015 Martin Micunda  \n\n    Permission is hereby granted, free of charge, to any person obtaining a copy\n    of this software and associated documentation files (the \"Software\"), to deal\n    in the Software without restriction, including without limitation the rights\n    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n    copies of the Software, and to permit persons to whom the Software is\n    furnished to do so, subject to the following conditions:\n    \n    The above copyright notice and this permission notice shall be included in\n    all copies or substantial portions of the Software.\n    \n    THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n    THE SOFTWARE.\n"
  },
  {
    "path": "server/index.js",
    "content": "/**\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license\t  The MIT License {@link http://opensource.org/licenses/MIT}\n */\n'use strict';\n\n/**\n * Module dependencies.\n */\nvar colors  = require('colors');\nvar logger  = require('mm-node-logger')(module);\nvar pkg     = require('./package.json');\nvar config  = require('./src/config/config');\nvar express = require('./src/config/express');\nvar mongodb = require('./src/config/mongoose');\n\n// Initialize mongoose\nmongodb(function startServer() {\n    // Initialize express\n    var app = express.init();\n\n    // Start up the server on the port specified in the config after we connected to mongodb\n    app.listen(config.server.port, function () {\n        var serverBanner = ['',\n            '*************************************' + ' EXPRESS SERVER '.yellow + '********************************************',\n            '*',\n            '* ' + pkg.description ,\n            '* @version ' + pkg.version,\n            '* @author ' + pkg.author.name,\n            '* @copyright ' + new Date().getFullYear() + ' ' + pkg.author.name,\n            '* @license ' + pkg.license.type + ', ' + pkg.license.url,\n            '*',\n            '*' + ' App started on port: '.blue + config.server.port + ' - with environment: '.blue + config.environment.blue,\n            '*',\n            '*************************************************************************************************',\n            ''].join('\\n');\n        logger.info(serverBanner);\n    });\n\n    module.exports = app;\n});\n\n"
  },
  {
    "path": "server/package.json",
    "content": "{\n    \"name\": \"ionic-photo-gallery-server\",\n    \"version\": \"0.0.6\",\n    \"description\": \"A hybrid app with authentication that allows registered users view a gallery of photos they have uploaded via the camera phone.\",\n    \"author\": {\n        \"name\": \"Martin Micunda\",\n        \"url\": \"http://martinmicunda.com\"\n    },\n    \"main\": \"index.js\",\n    \"scripts\": {\n        \"start\": \"node index.js\",\n        \"dev\": \"nodemon index -w 'src/**/*' --ext 'js json'\",\n        \"debug\": \"nodemon index --debug -w 'src/**/*' --ext 'js json'\",\n        \"lint\": \"jshint src/**/*.js\",\n        \"audit\": \"nsp audit-shrinkwrap && nsp audit-package\",\n        \"missing\": \"npm ls --depth 1\",\n        \"outdated\": \"npm outdated --depth 0\",\n        \"prepush\": \"npm shrinkwrap && npm test\",\n        \"postmerge\": \"npm install\",\n        \"pretest\": \"npm run lint\"\n    },\n    \"pre-commit\": [\n        \"lint\",\n        \"audit\",\n        \"missing\",\n        \"outdated\"\n    ],\n    \"repository\": {\n        \"type\": \"git\",\n        \"url\": \"https://github.com/martinmicunda/ionic-photo-gallery\"\n    },\n    \"bugs\": {\n        \"url\": \"https://github.com/martinmicunda/ionic-photo-gallery/issues\"\n    },\n    \"files\": [\n        \"src\",\n        \"index.js\",\n        \"LICENSE\",\n        \"README.md\"\n    ],\n    \"keywords\": [\n        \"mm\",\n        \"ionic\",\n        \"node\",\n        \"express\",\n        \"mobile\"\n    ],\n    \"dependencies\": {\n        \"express\": \"^4.12.3\",\n        \"mm-node-logger\": \"^0.0.*\",\n        \"colors\": \"^1.0.3\",\n        \"morgan\": \"^1.5.2\",\n        \"helmet\": \"^0.7.0\",\n        \"body-parser\": \"^1.12.2\",\n        \"method-override\": \"^2.3.2\",\n        \"passport\": \"^0.2.1\",\n        \"passport-local\": \"^1.0.0\",\n        \"mongoose\": \"^3.8.25\",\n        \"redis\": \"^0.12.1\",\n        \"jsonwebtoken\": \"^4.2.1\",\n        \"path\": \"^0.11.14\",\n        \"glob\": \"^5.0.3\",\n        \"lodash\": \"^3.5.0\",\n        \"bcryptjs\": \"^2.1.0\",\n        \"cors\": \"^2.5.3\",\n        \"multer\": \"^0.1.8\"\n    },\n    \"devDependencies\": {\n        \"nsp\": \"^1.0.0\",\n        \"jshint\": \"latest\",\n        \"nodemon\": \"^1.3.7\"\n    },\n    \"license\": {\n        \"type\": \"MIT\",\n        \"url\": \"https://github.com/martinmicunda/ionic-photo-gallery/master/LICENSE\"\n    },\n    \"engines\": {\n        \"node\": \">=0.12\",\n        \"npm\": \">=2.x\"\n    }\n}\n"
  },
  {
    "path": "server/src/authentication/authentication.config.js",
    "content": "/**\n * Authentication configuration.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license\t  The MIT License {@link http://opensource.org/licenses/MIT}\n */\n'use strict';\n\n/**\n * Module dependencies.\n */\nvar path      = require('path');\nvar passport  = require('passport');\nvar User      = require('../user/user.model.js');\nvar config    = require('../config/config');\nvar pathUtils = require('../utils/path-utils');\n\n\nmodule.exports = function(app) {\n    // Initialize strategies\n    pathUtils.getGlobbedPaths(path.join(__dirname, './strategies/**/*.js')).forEach(function(strategy) {\n        require(path.resolve(strategy))(User, config);\n    });\n\n    // Add passport's middleware\n    app.use(passport.initialize());\n};\n"
  },
  {
    "path": "server/src/authentication/authentication.controller.js",
    "content": "/**\n * Authentication controller.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license\t  The MIT License {@link http://opensource.org/licenses/MIT}\n */\n'use strict';\n\n/**\n * Module dependencies.\n */\nvar logger   = require('mm-node-logger')(module);\nvar passport = require('passport');\nvar token    = require('./token.controller.js');\nvar User     = require('../user/user.model.js');\n\n/**\n * Signin with email after passport authentication.\n *\n * @param {Object} req  The request object\n * @param {Object} res  The request object\n * @param {Object} next The request object\n * @returns {Object} the new created JWT token\n * @api public\n */\nfunction signin(req, res, next) {\n    passport.authenticate('local', function (err, user, info) {\n        var error = err || info;\n        if (error) return res.status(401).send(error);\n\n        // Remove sensitive data before login\n        user.password = undefined;\n        user.salt = undefined;\n\n        token.createToken(user, function(res, err, token) {\n            if(err) {\n                logger.error(err);\n                return res.status(400).send(err);\n            }\n\n            res.status(201).json({token: token});\n        }.bind(null, res));\n    })(req, res, next)\n}\n\n/**\n * Signout user and expire token.\n *\n * @param {Object} req  The request object\n * @param {Object} res  The request object\n * @api public\n */\nfunction signout(req, res) {\n    token.expireToken(req.headers, function(err, success) {\n        if (err) {\n            logger.error(err.message);\n            return res.status(401).send(err.message);\n        }\n\n        if(success) {\n            delete req.user;\n            res.sendStatus(200);\n        } else {\n            res.sendStatus(401);\n        }\n    });\n}\n\n/**\n * Create new user and login user in.\n *\n * @param {Object} req The request object\n * @param {Object} res The response object\n * @returns {Object} the new created JWT token\n * @api public\n */\nfunction signup(req, res) {\n    var email = req.body.email || '';\n    var password = req.body.password || '';\n\n    if (email == '' || password == '') {\n        return res.sendStatus(400);\n    }\n\n    // Init Variables\n    var user = new User(req.body);\n    // Add missing user fields\n    user.provider = 'local';\n\n    // Then save the user\n    user.save(function(err, user) {\n        if (err) {\n            logger.error(err.message);\n            return res.status(400).send(err);\n        } else {\n            // Remove sensitive data before login\n            user.password = undefined;\n            user.salt = undefined;\n\n            token.createToken(user, function(res, err, token) {\n                if (err) {\n                    logger.error(err.message);\n                    return res.status(400).send(err);\n                }\n\n                res.status(201).json({token: token});\n            }.bind(null, res));\n        }\n    });\n}\n\n/**\n * Middleware to verify the token and attaches the user object\n * to the request if authenticated.\n *\n * @param {Object} req  The request object\n * @param {Object} res  The request object\n * @param {Object} next The request object\n * @api public\n */\nfunction isAuthenticated(req, res, next) {\n    token.verifyToken(req.headers, function(next, err, data) {\n        if (err) {\n            logger.error(err.message);\n            return res.status(401).send(err.message);\n        }\n\n        req.user = data;\n\n        next();\n    }.bind(null, next));\n}\n\nmodule.exports = {\n    signin: signin,\n    signout: signout,\n    signup: signup,\n    isAuthenticated: isAuthenticated\n};\n"
  },
  {
    "path": "server/src/authentication/authentication.routes.js",
    "content": "/**\n * Authentication routes.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license\t  The MIT License {@link http://opensource.org/licenses/MIT}\n */\n'use strict';\n\n/**\n * Module dependencies.\n */\nvar authentication = require('./authentication.controller.js');\n\n/**\n * Set authentication routes.\n *\n * @param {Object} app The express application\n */\nfunction setAuthenticationRoutes(app) {\n    app.route('/auth/signin').post(authentication.signin);\n    app.route('/auth/signout').get(authentication.signout);\n    app.route('/auth/signup').post(authentication.signup);\n}\n\nmodule.exports = setAuthenticationRoutes;\n"
  },
  {
    "path": "server/src/authentication/strategies/local.js",
    "content": "/**\n * Authentication local strategy module.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license\t  The MIT License {@link http://opensource.org/licenses/MIT}\n */\n'use strict';\n\n/**\n * Module dependencies.\n */\nvar passport      = require('passport');\nvar LocalStrategy = require('passport-local').Strategy;\n\nfunction localStrategy(User, config) {\n    passport.use(new LocalStrategy({\n            usernameField: 'email',\n            passwordField: 'password'\n        },\n        function(email, password, callback) {\n            User.findOne({\n                email: email.toLowerCase()\n            }, function(err, user) {\n                if (err) return callback(err);\n\n                // no user found with that email\n                if (!user) {\n                    return callback(null, false, { message: 'The email is not registered.' });\n                }\n                // make sure the password is correct\n                user.comparePassword(password, function(err, isMatch) {\n                    if (err) { return callback(err); }\n\n                    // password did not match\n                    if (!isMatch) {\n                        return callback(null, false, { message: 'The password is not correct.' });\n                    }\n\n                    // success\n                    return callback(null, user);\n                });\n            });\n        }\n    ));\n}\n\nmodule.exports = localStrategy;\n"
  },
  {
    "path": "server/src/authentication/token.controller.js",
    "content": "/**\n * Token controller.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license\t  The MIT License {@link http://opensource.org/licenses/MIT}\n */\n'use strict';\n\n/**\n * Module dependencies.\n */\nvar jwt    = require('jsonwebtoken');\nvar redis  = require('../config/redis');\nvar config = require('../config/config');\n\n/**\n * Extract the token from the header Authorization.\n *\n * @method extractTokenFromHeader\n * @param {Object} headers The request headers\n * @returns {String} the token\n * @private\n */\nfunction extractTokenFromHeader(headers) {\n    if (headers == null) throw new Error('Header is null');\n    if (headers.authorization == null) throw new Error('Authorization header is null');\n\n    var authorization = headers.authorization;\n    var authArr = authorization.split(' ');\n    if (authArr.length !== 2) throw new Error('Authorization header value is not of length 2');\n\n    // retrieve token\n    var token = authArr[1];\n\n    // verify token\n    try {\n        jwt.verify(token, config.token.secret);\n    } catch(err) {\n        throw new Error('The token is not valid');\n    }\n\n    return token;\n}\n\n/**\n * Create a new JWT token and stores it in redis with payload data for a particular period of time.\n *\n * @method createToken\n * @param {Object}   payload An additional information that we can pass with token e.g. {user: 2, admin: true}\n * @param {Function} cb      Callback function\n * @returns {Function} callback function `callback(null, token)` if successfully created\n */\nfunction createToken(payload, cb) {\n    var ttl = config.token.expiration;\n\n    if(payload != null && typeof payload !== 'object') { return cb(new Error('payload is not an Object')) }\n    if(ttl != null && typeof ttl !== 'number') { return cb(new Error('ttl is not a valid Number')) }\n\n    /**\n     * Token is divided in 3 parts:\n     *  - header\n     *  - payload (It contains some additional information that we can pass with token e.g. {user: 2, admin: true}. This gets encoded into base64.)\n     *  - signature\n     *\n     * Token is something like xxxxxxxxxxx.yyyy.zzzzzzzzzzzz. Where the x is the encoded header, the y is the encoded payload and\n     * the z is the signature. So on front-end we can decode the yyyy part (the payload) if we need.\n     */\n    var token = jwt.sign(payload, config.token.secret, { expiresInMinutes: config.token.expiration });\n\n    if(redis) {\n        // stores a token with payload data for a ttl period of time\n        redis.setex(token, ttl, JSON.stringify(payload), function (token, err, reply) {\n            if (err) {\n                return cb(err);\n            }\n\n            if (reply) {\n                cb(null, token);\n            } else {\n                cb(new Error('Token not set in Redis'));\n            }\n        }.bind(null, token));\n    } else {\n        cb(null, token);\n    }\n}\n\n/**\n * Expires a token by deleting the entry in redis.\n *\n * @method expireToken\n * @param {Object}   headers The request headers\n * @param {Function} cb      Callback function\n * @returns {Function} callback function `callback(null, true)` if successfully deleted\n */\nfunction expireToken(headers, cb) {\n    try {\n        var token = extractTokenFromHeader(headers);\n\n        if(token == null) {return cb(new Error('Token is null'));}\n\n        if(redis) {\n            // delete token from redis\n            redis.del(token, function (err, reply) {\n                if (err) {\n                    return cb(err);\n                }\n\n                if (!reply) {\n                    return cb(new Error('Token not found'));\n                }\n\n                return cb(null, true);\n            });\n        } else {\n            cb(null, true);\n        }\n    } catch (err) {\n        return cb(err);\n    }\n}\n\n/**\n * Verify if token is valid.\n *\n * @method verifyToken\n * @param {Object}   headers The request headers\n * @param {Function} cb      Callback function\n * @returns {Function} callback function `callback(null, JSON.parse(userData))` if token exist\n */\nfunction verifyToken(headers, cb) {\n    try {\n        var token = extractTokenFromHeader(headers);\n\n        if(token == null) {return cb(new Error('Token is null'));}\n\n        if(redis) {\n            // gets the associated data of the token\n            redis.get(token, function(err, userData) {\n                if(err) {return cb(err);}\n\n                if(!userData) {return cb(new Error('Token not found'));}\n\n                return cb(null, JSON.parse(userData));\n            });\n        } else {\n            cb(null, true);\n        }\n    } catch (err) {\n        return cb(err);\n    }\n}\n\nmodule.exports = {\n    createToken: createToken,\n    expireToken: expireToken,\n    verifyToken: verifyToken\n};\n"
  },
  {
    "path": "server/src/config/config.js",
    "content": "/**\n * An application configuration.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license\t  The MIT License {@link http://opensource.org/licenses/MIT}\n */\n'use strict';\n\nvar config = {};\n\nconfig.environment = process.env.NODE_ENV || 'development';\n\n// Upload files in memory\nconfig.uploadFilesInMemory = process.env.UPLOAD_FILES_IN_MEMORY || false;\n\n// Populate the DB with sample data\nconfig.seedDB = true;\n\n// Token settings\nconfig.token = {\n    secret: process.env.TOKEN_SECRET || 'ionic-photo-gallery',\n    expiration: process.env.TOKEN_EXPIRATION || 60*60*24 //24 hours\n};\n\n// Server settings\nconfig.server = {\n    host: '0.0.0.0',\n    port: process.env.NODE_PORT || process.env.PORT || 3000\n};\n\n// MongoDB settings\nconfig.mongodb = {\n    dbURI: process.env.MONGODB_URI || process.env.MONGOLAB_URI || \"mongodb://127.0.0.1:27017/ionic-photo-gallery\",\n    dbOptions: {\"user\": \"\", \"pass\": \"\"}\n};\n\n\n// Redis settings\nif (process.env.REDISTOGO_URL) {\n    var rtg = require('url').parse(process.env.REDISTOGO_URL);\n    process.env.REDIS_HOST = rtg.hostname;\n    process.env.REDIS_PORT = rtg.port;\n    process.env.REDIS_AUTH = rtg.auth.split(\":\")[1];\n}\nconfig.redis = {\n    isAvailable: process.env.IS_REDIS_AVAILABLE || true,\n    host: process.env.REDIS_HOST || '127.0.0.1',\n    port: process.env.REDIS_PORT || 6379,\n    auth: process.env.REDIS_AUTH || '',\n    options: {}\n};\n\n// Export configuration object\nmodule.exports = config;\n"
  },
  {
    "path": "server/src/config/express.js",
    "content": "/**\n * Express configuration.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license\t  The MIT License {@link http://opensource.org/licenses/MIT}\n */\n'use strict';\n\n/**\n * Module dependencies.\n */\nvar cors           = require('cors');\nvar path           = require('path');\nvar morgan         = require('morgan');\nvar helmet         = require('helmet');\nvar multer         = require('multer');\nvar logger         = require('mm-node-logger')(module);\nvar express        = require('express');\nvar bodyParser     = require('body-parser');\nvar methodOverride = require('method-override');\nvar pathUtils      = require('../utils/path-utils');\nvar config         = require('./config');\n\n/**\n * Initialize application middleware.\n *\n * @method initMiddleware\n * @param {Object} app The express application\n * @private\n */\nfunction initMiddleware(app) {\n    // Showing stack errors\n    app.set('showStackError', true);\n\n    // Enable jsonp\n    app.enable('jsonp callback');\n\n    // Environment dependent middleware\n    if (config.environment === 'development') {\n        // Enable logger (morgan)\n        app.use(morgan('dev'));\n\n        // Disable views cache\n        app.set('view cache', false);\n    } else if (config.environment === 'production') {\n        app.locals.cache = 'memory';\n    }\n\n    // Request body parsing middleware should be above methodOverride\n    app.use(bodyParser.urlencoded({\n        extended: true\n    }));\n    app.use(bodyParser.json());\n    app.use(methodOverride());\n\n    // Add multipart handling middleware\n    app.use(multer({\n        dest: './uploads/',\n        inMemory: config.uploadFilesInMemory\n    }));\n\n    // Setting router and the static folder for uploaded files\n    app.use('/uploads', express.static(path.resolve('./uploads')));\n}\n\n/**\n * Configure Helmet headers configuration.\n *\n * @method initHelmetHeaders\n * @param {Object} app The express application\n * @private\n */\nfunction initHelmetHeaders(app) {\n    // Use helmet to secure Express headers\n    app.use(helmet.xframe());\n    app.use(helmet.xssFilter());\n    app.use(helmet.nosniff());\n    app.use(helmet.ienoopen());\n    app.disable('x-powered-by');\n}\n\n/**\n * Configure CORS (Cross-Origin Resource Sharing) headers to support Cross-site HTTP requests.\n *\n * @method initCrossDomain\n * @param {Object} app The express application\n * @private\n */\nfunction initCrossDomain(app) {\n    // setup CORS\n    app.use(cors());\n    app.use(function(req, res, next) {\n        // Website you wish to allow to connect\n        res.set('Access-Control-Allow-Origin', '*');\n        // Request methods you wish to allow\n        res.set('Access-Control-Allow-Methods', 'GET, POST, DELETE, PUT');\n        // Request headers you wish to allow\n        res.set('Access-Control-Allow-Headers', 'Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token');\n\n        // Pass to next layer of middleware\n        next();\n    });\n}\n\n/**\n * Configure app modules config files.\n *\n * @method initGonfig\n * @param {Object} app The express application\n * @private\n */\nfunction initGonfig(app) {\n    // Globbing config files\n    pathUtils.getGlobbedPaths(path.join(__dirname, '../**/*.config.js')).forEach(function (routePath) {\n        require(path.resolve(routePath))(app);\n    });\n}\n\n/**\n * Configure app routes.\n *\n * @method initRoutes\n * @param {Object} app The express application\n * @private\n */\nfunction initRoutes(app) {\n    // Globbing routing files\n    pathUtils.getGlobbedPaths(path.join(__dirname, '../**/*.routes.js')).forEach(function (routePath) {\n        require(path.resolve(routePath))(app);\n    });\n}\n\n/**\n * Configure error handling.\n *\n * @method initErrorRoutes\n * @param {Object} app The express application\n * @private\n */\nfunction initErrorRoutes(app) {\n    // Assume 'not found' in the error msgs is a 404. this is somewhat silly, but valid, you can do whatever you like, set properties, use instanceof etc.\n    app.use(function (err, req, res, next) {\n        // If the error object doesn't exists\n        if (!err) return next();\n\n        // Log it\n        logger.error('Internal error(%d): %s', res.statusCode, err.stack);\n\n        // Redirect to error page\n        res.sendStatus(500);\n    });\n\n    // Assume 404 since no middleware responded\n    app.use(function (req, res) {\n        // Redirect to not found page\n        res.sendStatus(404);\n    });\n}\n\n/**\n * Populate DB with sample data.\n *\n * @method initDB\n * @private\n */\nfunction initDB() {\n    if(config.seedDB) {\n        require('./seed');\n    }\n}\n/**\n * Initialize the Express application.\n *\n * @method init\n * @returns {Object} the express application\n */\nfunction init() {\n    // Initialize express app\n    var app = express();\n\n    // Initialize Express middleware\n    initMiddleware(app);\n\n    // Initialize Helmet security headers\n    initHelmetHeaders(app);\n\n    // Initialize CORS\n    initCrossDomain(app);\n\n    // Initialize config\n    initGonfig(app);\n\n    // Initialize routes\n    initRoutes(app);\n\n    // Initialize error routes\n    initErrorRoutes(app);\n\n    // Initialize DB with sample data\n    initDB();\n\n    return app;\n}\n\nmodule.exports.init = init;\n"
  },
  {
    "path": "server/src/config/mongoose.js",
    "content": "/**\n * This module follow best practice for creating, maintaining and using a Mongoose connection like:\n *  - open the connection when the app process start\n *  - start the app server when after the database connection is open (optional)\n *  - monitor the connection events (`connected`, `error` and `disconnected`)\n *  - close the connection when the app process terminates\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license\t  The MIT License {@link http://opensource.org/licenses/MIT}\n */\n'use strict';\n\n/**\n * Module dependencies.\n */\nvar logger   = require('mm-node-logger')(module);\nvar mongoose = require('mongoose');\nvar config = require('./config');\n\n/**\n * Create mongoose connection.\n *\n * @param {*=} cb The callback that start server\n */\nfunction createMongooseConnection(cb) {\n    // create the database connection\n    mongoose.connect(config.mongodb.dbURI, config.mongodb.dbOptions);\n\n    // when successfully connected\n    mongoose.connection.on('connected', function () {\n        logger.info('Mongoose connected to ' + config.mongodb.dbURI);\n    });\n\n    // if the connection throws an error\n    mongoose.connection.on('error', function (err) {\n        logger.error('Mongoose connection error: ' + err);\n    });\n\n    // when the connection is disconnected\n    mongoose.connection.on('disconnected', function () {\n        logger.info('Mongoose disconnected');\n    });\n\n    // when the connection is open\n    mongoose.connection.once('open', function () {\n        if(cb && typeof(cb) === 'function') {cb();}\n    });\n\n    // if the Node process ends, close the Mongoose connection\n    process.on('SIGINT', function() {\n        mongoose.connection.close(function () {\n            logger.info('Mongoose disconnected through app termination');\n            process.exit(0);\n        });\n    });\n}\n\nmodule.exports = createMongooseConnection;\n"
  },
  {
    "path": "server/src/config/redis.js",
    "content": "/**\n * Redis configuration.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license\t  The MIT License {@link http://opensource.org/licenses/MIT}\n */\n'use strict';\n\n/**\n * Module dependencies.\n */\nvar redis  = require('redis');\nvar logger = require('mm-node-logger')(module);\nvar config = require('./config');\n\nvar redisClient = null;\n\nif(config.redis.isAvailable) {\n    redisClient = redis.createClient(config.redis.port, config.redis.host);\n\n    redisClient.auth(config.redis.auth);\n\n    redisClient.on('connect', function () {\n        logger.info('Redis connected to ' + config.redis.host + ':' + config.redis.port);\n    });\n\n    redisClient.on('error', function (err) {\n        logger.error('Redis error: ' + err);\n    });\n}\n\n\nmodule.exports = redisClient;\n\n"
  },
  {
    "path": "server/src/config/seed.js",
    "content": "/**\n * Populate DB with sample data on server start to disable, edit config.js, and set `seedDB: false`\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license   The MIT License {@link http://opensource.org/licenses/MIT}\n */\n'use strict';\n\n/**\n * Module dependencies.\n */\nvar logger   = require('mm-node-logger')(module);\nvar mongoose = require('mongoose');\nvar User     = require('../user/user.model');\nvar Image    = require('../image/image.model');\n\nvar testUserId = mongoose.Types.ObjectId();\n\nUser.find({}).remove(function() {\n    User.create({\n            provider: 'local',\n            name: 'Martin Micunda',\n            email: 'martinmicunda@test.com',\n            password: 'test',\n            avatar: 'https://avatars2.githubusercontent.com/u/1643606?v=3'\n        }, {\n            _id: testUserId,\n            provider: 'local',\n            name: 'Test',\n            email: 'test@test.com',\n            password: 'test'\n        }, {\n            provider: 'local',\n            name: 'Admin',\n            email: 'admin@admin.com',\n            password: 'admin'\n        }, function() {\n            logger.info('Finished populating users');\n        }\n    );\n});\n\nImage.find({}).remove(function() {\n    Image.create({\n        fileName : 'Slovakia 1',\n        url : 'http://www.rocketroute.com/wp-content/uploads/Carpathian-mountains-Slovakia-685x458.jpg?125416',\n        user: testUserId\n    }, {\n        fileName : 'Slovakia 2',\n        url : 'http://www.travelslovakia.sk/images/blog/small-group-tours/tatra-mountains-self-guided.jpg?125416',\n        user: testUserId\n    }, {\n        fileName : 'Slovakia 3',\n        url : 'http://www.travelslovakia.sk/images/day-tours/high-tatras.jpg?125416',\n        user: testUserId\n    }, function() {\n        logger.info('Finished populating images');\n    });\n});\n"
  },
  {
    "path": "server/src/image/image.controller.js",
    "content": "/**\n * User controller.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license\t  The MIT License {@link http://opensource.org/licenses/MIT}\n */\n'use strict';\n\n/**\n * Module dependencies.\n */\nvar path   = require('path');\nvar logger = require('mm-node-logger')(module);\nvar Image  = require('./image.model.js');\n\n/**\n * Find list of images by user id.\n *\n * @param {Object} req The request object\n * @param {Object} res The request object\n * @returns {Array} the list of images corresponding to the specified user id\n * @api public\n */\nfunction findByUser(req, res) {\n    return Image.find({user: req.query.userId}, function (err, images) {\n        if (err) {\n            logger.error(err.message);\n            return res.status(400).send(err);\n        } else {\n            return res.json(images);\n        }\n    });\n}\n\n/**\n * Create image.\n *\n * @param {Object} req The request object\n * @param {Object} res The request object\n * @returns {Object} the new create image\n * @api public\n */\nfunction create(req, res) {\n    var image = new Image();\n    image.fileName = req.files.image.name;\n    image.url = path.join(req.body.url, req.files.image.path);\n    image.user = req.body.userId;\n\n    image.save(function(err, image) {\n        if (err) {\n            logger.error(err.message);\n            return res.status(400).send(err);\n        } else {\n            res.status(201).json(image);\n        }\n    });\n}\n\n/**\n * Delete image.\n *\n * @param {Object} req The request object\n * @param {Object} res The request object\n * @api public\n */\nfunction deleteImage(req, res) {\n    Image.findByIdAndRemove(req.params.id, function(err) {\n        if (err) {\n            logger.error(err.message);\n            return res.status(500).send(err);\n        } else {\n            res.sendStatus(204);\n        }\n    });\n}\n\nmodule.exports = {\n    findByUser: findByUser,\n    create: create,\n    delete: deleteImage\n};\n"
  },
  {
    "path": "server/src/image/image.model.js",
    "content": "/**\n * Image model.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license\t  The MIT License {@link http://opensource.org/licenses/MIT}\n */\n'use strict';\n\n/**\n * Module dependencies.\n */\nvar mongoose = require('mongoose');\nvar User = require('../user/user.model.js');\n\n/**\n * Image Schema\n */\nvar ImageSchema = new mongoose.Schema({\n    fileName: {\n        type: String\n    },\n    url: {\n        type: String,\n        trim: true,\n        required: true\n    },\n    user: {\n        type: mongoose.Schema.Types.ObjectId,\n        ref: User\n    }\n});\n\nmodule.exports = mongoose.model('Image', ImageSchema);\n"
  },
  {
    "path": "server/src/image/image.routes.js",
    "content": "/**\n * Image routes.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license\t  The MIT License {@link http://opensource.org/licenses/MIT}\n */\n'use strict';\n\n/**\n * Module dependencies.\n */\nvar image          = require('./image.controller.js');\nvar authentication = require('../authentication/authentication.controller.js');\n\n/**\n * Set image routes.\n *\n * @param {Object} app The express application\n */\nfunction setImageRoutes(app) {\n    app.route('/images')\n        .post(authentication.isAuthenticated, image.create)\n        .get(authentication.isAuthenticated, image.findByUser);\n\n    app.route('/images/:id').delete(authentication.isAuthenticated, image.delete);\n\n}\n\nmodule.exports = setImageRoutes;\n"
  },
  {
    "path": "server/src/user/user.controller.js",
    "content": "/**\n * User controller.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license\t  The MIT License {@link http://opensource.org/licenses/MIT}\n */\n'use strict';\n\n/**\n * Module dependencies.\n */\nvar logger = require('mm-node-logger')(module);\nvar User   = require('./user.model.js');\n\n/**\n * Find an user by id.\n *\n * @param {Object} req The request object\n * @param {Object} res The request object\n * @returns {Object} the user corresponding to the specified id\n * @api public\n */\nfunction findById(req, res) {\n    return User.findById(req.params.id, 'name email avatar', function (err, user) {\n        if (err) {\n            logger.error(err.message);\n            return res.status(400).send(err);\n        } else {\n            res.json(user);\n        }\n    });\n}\n\n/**\n * List of users.\n *\n * @param {Object} req The request object\n * @param {Object} res The request object\n * @returns {Array} the list of users\n * @api public\n */\nfunction findAll(req, res) {\n    User.find(function(err, users) {\n        if (err) {\n            logger.error(err.message);\n            return res.status(400).send(err);\n        } else {\n            res.json(users);\n        }\n    });\n}\n\nmodule.exports = {\n    findById: findById,\n    findAll: findAll\n};\n"
  },
  {
    "path": "server/src/user/user.model.js",
    "content": "/**\n * User model.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license\t  The MIT License {@link http://opensource.org/licenses/MIT}\n */\n'use strict';\n\n/**\n * Module dependencies.\n */\nvar bcrypt   = require('bcryptjs');\nvar mongoose = require('mongoose');\n\nvar SALT_WORK_FACTOR = 10;\nvar authTypes = ['github', 'twitter', 'facebook', 'google'];\n\n/**\n * A Validation function for local strategy properties\n */\nvar validateLocalStrategyProperty = function(property) {\n    return ((this.provider !== 'local' && !this.updated) || property.length);\n};\n\n/**\n * User Schema\n */\nvar UserSchema = new mongoose.Schema({\n    name: {\n        type: String,\n        trim: true,\n        validate: [validateLocalStrategyProperty, 'Please fill in your name']\n    },\n    email: {\n        type: String,\n        trim: true,\n        unique: true,\n        required: true,\n        lowercase: true,\n        validate: [validateLocalStrategyProperty, 'Please fill in your email'],\n        match: [/.+\\@.+\\..+/, 'Please fill a valid email address']\n    },\n    password: {\n        type: String,\n        required: true\n    },\n    salt: {\n        type: String\n    },\n    avatar: {\n        type: String,\n        default: 'https://raw.githubusercontent.com/martinmicunda/employee-scheduling-ui/master/src/images/anonymous.jpg?123456'\n    },\n    provider: {\n        type: String,\n        required: 'Provider is required'\n    },\n    updated: {\n        type: Date\n    },\n    created: {\n        type: Date,\n        default: Date.now\n    }\n});\n\n/**\n * Validations\n */\n// Validate empty email\nUserSchema\n    .path('email')\n    .validate(function(email) {\n        // if you are authenticating by any of the oauth strategies, don't validate\n        if (authTypes.indexOf(this.provider) !== -1) return true;\n        return email.length;\n    }, 'Email cannot be blank');\n\n// Validate empty password\nUserSchema\n    .path('password')\n    .validate(function(password) {\n        // if you are authenticating by any of the oauth strategies, don't validate\n        if (authTypes.indexOf(this.provider) !== -1) return true;\n        return password.length;\n    }, 'Password cannot be blank');\n\n// Validate email is not taken\nUserSchema\n    .path('email')\n    .validate(function(value, respond) {\n        var self = this;\n        this.constructor.findOne({email: value}, function(err, user) {\n            if(err) throw err;\n            if(user) {\n                if(self.id === user.id) return respond(true);\n                return respond(false);\n            }\n            respond(true);\n        });\n    }, 'The specified email address is already in use.');\n\n/**\n * Pre-save hook (execute before each user.save() call)\n */\nUserSchema.pre('save', function(next) {\n    var user = this;\n\n    // only hash the password if it has been modified (or is new)\n    if (!user.isModified('password')) { return next(); }\n\n    // password changed so we need to hash it (generate a salt)\n    bcrypt.genSalt(SALT_WORK_FACTOR, function(err, salt) {\n        if (err) { return next(err); }\n\n        // TODO (martin): is it good idea to store salt?\n        // store salt\n        user.salt = salt;\n\n        // hash the password using our new salt\n        bcrypt.hash(user.password, salt, function(err, hash) {\n            if (err) { return next(err); }\n\n            // override the cleartext password with the hashed one\n            user.password = hash;\n            next();\n        });\n    });\n});\n\n/**\n * Check if the passwords are the same.\n *\n * @param {String}   password The user password\n * @param {Function} cb       Callback function\n * @returns {Function} callback function `callback(null, true)` if password matched\n */\nUserSchema.methods.comparePassword = function(password, cb) {\n    bcrypt.compare(password, this.password, function(err, isMatch) {\n        cb(err, isMatch);\n    });\n};\n\nmodule.exports = mongoose.model('User', UserSchema);\n"
  },
  {
    "path": "server/src/user/user.routes.js",
    "content": "/**\n * User routes.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license\t  The MIT License {@link http://opensource.org/licenses/MIT}\n */\n'use strict';\n\n/**\n * Module dependencies.\n */\nvar user           = require('./user.controller.js');\nvar authentication = require('../authentication/authentication.controller.js');\n\n/**\n * Set user routes.\n *\n * @param {Object} app The express application\n */\nfunction setUserRoutes(app) {\n    app.route('/users/:id').get(authentication.isAuthenticated, user.findById);\n    app.route('/users').get(authentication.isAuthenticated, user.findAll);\n}\n\nmodule.exports = setUserRoutes;\n"
  },
  {
    "path": "server/src/utils/path-utils.js",
    "content": "/**\n * Utils for path.\n *\n * @author    Martin Micunda {@link http://martinmicunda.com}\n * @copyright Copyright (c) 2015, Martin Micunda\n * @license\t  The MIT License {@link http://opensource.org/licenses/MIT}\n */\n'use strict';\n\n/**\n * Module dependencies.\n */\nvar _    = require('lodash');\nvar glob = require('glob');\nvar path = require('path');\n\n/**\n * Get files by glob patterns\n */\nfunction getGlobbedPaths(globPatterns, excludes) {\n    // URL paths regex\n    var urlRegex = new RegExp('^(?:[a-z]+:)?\\/\\/', 'i');\n\n    // The output array\n    var output = [];\n\n    // If glob pattern is array so we use each pattern in a recursive way, otherwise we use glob\n    if (_.isArray(globPatterns)) {\n        globPatterns.forEach(function(globPattern) {\n            output = _.union(output, getGlobbedPaths(globPattern, excludes));\n        });\n    } else if (_.isString(globPatterns)) {\n        if (urlRegex.test(globPatterns)) {\n            output.push(globPatterns);\n        } else {\n            var files = glob.sync(globPatterns);\n            if (excludes) {\n                files = files.map(function(file) {\n                    if (_.isArray(excludes)) {\n                        for (var i in excludes) {\n                            file = file.replace(excludes[i], '');\n                        }\n                    } else {\n                        file = file.replace(excludes, '');\n                    }\n                    return file;\n                });\n            }\n            output = _.union(output, files);\n        }\n    }\n\n    return output;\n}\n\nexports.getGlobbedPaths = getGlobbedPaths;\n"
  },
  {
    "path": "server/test/.gitKeep",
    "content": ""
  }
]