diff --git a/.babelrc b/.babelrc
new file mode 100644
index 00000000..c13c5f62
--- /dev/null
+++ b/.babelrc
@@ -0,0 +1,3 @@
+{
+ "presets": ["es2015"]
+}
diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 00000000..ca514748
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,21 @@
+{
+ "rules": {
+ "no-console": "off",
+ "indent": [ "error", 2 ],
+ "quotes": [ "error", "single" ],
+ "semi": ["error", "always"],
+ "linebreak-style": [ "error", "unix" ]
+ },
+ "env": {
+ "es6": true,
+ "node": true,
+ "mocha": true,
+ "jasmine": true
+ },
+ "ecmaFeatures": {
+ "modules": true,
+ "experimentalObjectRestSpread": true,
+ "impliedStrict": true
+ },
+ "extends": "eslint:recommended"
+}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..ac4aad84
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,129 @@
+
+# Created by https://www.gitignore.io/api/node,osx,windows,linux
+
+### Linux ###
+*~
+
+# temporary files which can be created if a process still has a handle open of a deleted file
+.fuse_hidden*
+
+# KDE directory preferences
+.directory
+
+# Linux trash folder which might appear on any partition or disk
+.Trash-*
+
+# .nfs files are created when an open file is removed but is still being accessed
+.nfs*
+
+### Node ###
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Typescript v1 declaration files
+typings/
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+
+
+### OSX ###
+*.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+### Windows ###
+# Windows thumbnail cache files
+Thumbs.db
+ehthumbs.db
+ehthumbs_vista.db
+
+# Folder config file
+Desktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# End of https://www.gitignore.io/api/node,osx,windows,linux
diff --git a/app/component/gallery/create-gallery/_create-gallery.scss b/app/component/gallery/create-gallery/_create-gallery.scss
new file mode 100644
index 00000000..0f2cc431
--- /dev/null
+++ b/app/component/gallery/create-gallery/_create-gallery.scss
@@ -0,0 +1,26 @@
+.create-gallery{
+ form{
+ fieldset{
+ margin: 20px auto;
+ label{
+ margin-left: 10%;
+ }
+ input{
+ display: block;
+ width: 80%;
+ height: 30px;
+ margin: 20px auto;
+ border-radius: 10px;
+ text-align: center;
+ }
+
+ }
+ button{
+ position: absolute;
+ padding-left: 15%;
+ padding-right: 15%;
+ right:10%;
+ height: 60px;
+ }
+ }
+}
diff --git a/app/component/gallery/create-gallery/create-gallery.html b/app/component/gallery/create-gallery/create-gallery.html
new file mode 100644
index 00000000..d31360ef
--- /dev/null
+++ b/app/component/gallery/create-gallery/create-gallery.html
@@ -0,0 +1,13 @@
+
diff --git a/app/component/gallery/create-gallery/create-gallery.js b/app/component/gallery/create-gallery/create-gallery.js
new file mode 100644
index 00000000..5bc3fb22
--- /dev/null
+++ b/app/component/gallery/create-gallery/create-gallery.js
@@ -0,0 +1,23 @@
+'use strict';
+
+require('./_create-gallery.scss');
+
+module.exports = {
+ template: require('./create-gallery.html'),
+ controller: ['$log', 'galleryService', CreateGalleryController],
+ controllerAs: 'createGalleryCtrl'
+};
+
+function CreateGalleryController($log, galleryService){
+ $log.debug('CreateGalleryController');
+
+ this.gallery ={};
+
+ this.createGallery=function(){
+ galleryService.createGallery(this.gallery)
+ .then(() => {
+ this.gallery.name = null;
+ this.gallery.desc = null;
+ });
+ };
+}
diff --git a/app/component/gallery/edit-gallery/_edit-gallery.scss b/app/component/gallery/edit-gallery/_edit-gallery.scss
new file mode 100644
index 00000000..25599875
--- /dev/null
+++ b/app/component/gallery/edit-gallery/_edit-gallery.scss
@@ -0,0 +1,35 @@
+.edit{
+ overflow: auto;
+ .edit-gallery{
+ background: #444;
+ border-radius: 10px;
+ width: 80%;
+ margin: 0px auto;
+ margin-top: 30px;
+ fieldset{
+ span{
+ display:block;
+ margin-top: 10px;
+ margin-left: 2%;
+ margin-right: 2%;
+ float:left;
+
+ }
+ input{
+ margin-top: 10px;
+ width: 88%;
+ }
+
+ }
+ fieldset:last-of-type{
+ input{
+ width:83%;
+ }
+ }
+ button{
+ margin-left: 2%;
+ margin-top: 10px;
+ margin-bottom: 10px;
+ }
+ }
+}
diff --git a/app/component/gallery/edit-gallery/edit-gallery.html b/app/component/gallery/edit-gallery/edit-gallery.html
new file mode 100644
index 00000000..17e642c2
--- /dev/null
+++ b/app/component/gallery/edit-gallery/edit-gallery.html
@@ -0,0 +1,21 @@
+
diff --git a/app/component/gallery/edit-gallery/edit-gallery.js b/app/component/gallery/edit-gallery/edit-gallery.js
new file mode 100644
index 00000000..dcebed99
--- /dev/null
+++ b/app/component/gallery/edit-gallery/edit-gallery.js
@@ -0,0 +1,21 @@
+'use strict';
+
+require('./_edit-gallery.scss');
+
+module.exports = {
+ template: require('./edit-gallery.html'),
+ controller: ['$log', 'galleryService', EditGalleryController],
+ controllerAs: 'editGalleryCtrl',
+ bindings: {
+ gallery: '<'
+ }
+};
+
+function EditGalleryController($log, galleryService){
+ $log.debug('EditGalleryController');
+
+ this.updateGallery = function(){
+ $log.debug('editGalleryCtrl.updateGallery');
+ galleryService.updateGallery(this.gallery._id, this.gallery);
+ };
+}
diff --git a/app/component/gallery/gallery-item/_gallery-item.scss b/app/component/gallery/gallery-item/_gallery-item.scss
new file mode 100644
index 00000000..84aa1ccc
--- /dev/null
+++ b/app/component/gallery/gallery-item/_gallery-item.scss
@@ -0,0 +1,30 @@
+.gallery-item{
+ .current-item{
+ background: #444;
+ width: 80%;
+ border-radius: 10px;
+ margin: 10px auto;
+ div{
+ overflow: auto;
+ width:80%;
+ span{
+ float:left;
+ margin-top: 10px;
+ display: block;
+ margin: 10px 20px;
+ }
+ }
+ }
+ .editremove{
+ clear:both;
+ display:block;
+ margin-left: 60%;
+ width: 30%;
+ span{
+ padding-left: 10%;
+ padding-right: 10%;
+ border-radius: 10px;
+ background-color: #444;
+ }
+ }
+}
diff --git a/app/component/gallery/gallery-item/gallery-item.html b/app/component/gallery/gallery-item/gallery-item.html
new file mode 100644
index 00000000..9534e344
--- /dev/null
+++ b/app/component/gallery/gallery-item/gallery-item.html
@@ -0,0 +1,20 @@
+
+
+
+ name:
+ {{ galleryItemCtrl.gallery.name }}
+
+
+
+ description:
+ {{ galleryItemCtrl.gallery.desc }}
+
+
+
+
+
+
diff --git a/app/component/gallery/gallery-item/gallery-item.js b/app/component/gallery/gallery-item/gallery-item.js
new file mode 100644
index 00000000..d01ebea6
--- /dev/null
+++ b/app/component/gallery/gallery-item/gallery-item.js
@@ -0,0 +1,20 @@
+'use strict';
+
+require('./_gallery-item.scss');
+
+module.exports = {
+ template: require('./gallery-item.html'),
+ controller: ['$log', 'galleryService', GalleryItemController],
+ controllerAs: 'galleryItemCtrl',
+ bindings: {
+ gallery: '<'
+ }
+};
+
+function GalleryItemController($log, galleryService){
+ $log.debug('GalleryItemController');
+ this.showEditGallery = false;
+ this.deleteGallery = function(){
+ galleryService.deleteGallery(this.gallery._id);
+ };
+}
diff --git a/app/component/gallery/thumbnail-container/_thumbnail-container.scss b/app/component/gallery/thumbnail-container/_thumbnail-container.scss
new file mode 100644
index 00000000..0952cc53
--- /dev/null
+++ b/app/component/gallery/thumbnail-container/_thumbnail-container.scss
@@ -0,0 +1,9 @@
+.thumbnail-container{
+ background: #444;
+ width: 80%;
+ margin: 10px auto;
+ border-radius: 10px;
+ h3{
+ margin-left: 10px;
+ }
+}
diff --git a/app/component/gallery/thumbnail-container/thumbnail-container.html b/app/component/gallery/thumbnail-container/thumbnail-container.html
new file mode 100644
index 00000000..e7d383b4
--- /dev/null
+++ b/app/component/gallery/thumbnail-container/thumbnail-container.html
@@ -0,0 +1,7 @@
+
+
{{thumbnailContainerCtrl.gallery.name}}
+
+
+
+
+
diff --git a/app/component/gallery/thumbnail-container/thumbnail-container.js b/app/component/gallery/thumbnail-container/thumbnail-container.js
new file mode 100644
index 00000000..18bf16d4
--- /dev/null
+++ b/app/component/gallery/thumbnail-container/thumbnail-container.js
@@ -0,0 +1,11 @@
+'use strict';
+
+require('./_thumbnail-container.scss');
+
+module.exports = {
+ template: require('./thumbnail-container.html'),
+ controllerAs: 'thumbnailContainerCtrl',
+ bindings: {
+ gallery: '<'
+ }
+};
diff --git a/app/component/gallery/thumbnail/_thumbnail.scss b/app/component/gallery/thumbnail/_thumbnail.scss
new file mode 100644
index 00000000..3f106203
--- /dev/null
+++ b/app/component/gallery/thumbnail/_thumbnail.scss
@@ -0,0 +1,8 @@
+.thumbnail{
+ width: 80%;
+ margin:auto;
+ img{
+ width:80%;
+ margin: 0px auto;
+ }
+}
diff --git a/app/component/gallery/thumbnail/thumbnail.html b/app/component/gallery/thumbnail/thumbnail.html
new file mode 100644
index 00000000..2489b1fa
--- /dev/null
+++ b/app/component/gallery/thumbnail/thumbnail.html
@@ -0,0 +1,4 @@
+
+
![{{thumbnailCtrl.pic.desc}}]()
+
delete
+
diff --git a/app/component/gallery/thumbnail/thumbnail.js b/app/component/gallery/thumbnail/thumbnail.js
new file mode 100644
index 00000000..72696f07
--- /dev/null
+++ b/app/component/gallery/thumbnail/thumbnail.js
@@ -0,0 +1,22 @@
+'use strict';
+
+require('./_thumbnail.scss');
+
+module.exports = {
+ template: require('./thumbnail.html'),
+ controller: ['$log', 'picService', thumbnailController],
+ controllerAs: 'thumbnailCtrl',
+ bindings: {
+ pic: '<',
+ gallery: '<'
+ }
+};
+
+function thumbnailController($log, picService){
+ $log.debug('thumbnailController');
+
+ this.deletePic = function(){
+ $log.debug('thumbnailCtrl.deletePic');
+ picService.deleteGalleryPic(this.gallery, this.pic._id);
+ };
+}
diff --git a/app/component/gallery/upload-pic/_upload-pic.scss b/app/component/gallery/upload-pic/_upload-pic.scss
new file mode 100644
index 00000000..ce8407fb
--- /dev/null
+++ b/app/component/gallery/upload-pic/_upload-pic.scss
@@ -0,0 +1,33 @@
+.upload-pic{
+ background: #444;
+ border-radius: 10px;
+ overflow: auto;
+ h3{
+ margin-top: 10px;
+ }
+ form {
+ fieldset{
+ width: 80%;
+ margin: 10px auto;
+ input{
+ width: 100%;
+ margin: 10px auto;
+ }
+ }
+ div{
+ p{
+ margin-left: 30%;
+ margin-right: 5%;
+ text-decoration: underline;
+ float: left;
+ &:hover{
+ cursor: pointer;
+ }
+ }
+ button{
+ padding-left: 5%;
+ padding-right: 5%;
+ }
+ }
+ }
+}
diff --git a/app/component/gallery/upload-pic/upload-pic.html b/app/component/gallery/upload-pic/upload-pic.html
new file mode 100644
index 00000000..1b8fb4a8
--- /dev/null
+++ b/app/component/gallery/upload-pic/upload-pic.html
@@ -0,0 +1,20 @@
+
diff --git a/app/component/gallery/upload-pic/upload-pic.js b/app/component/gallery/upload-pic/upload-pic.js
new file mode 100644
index 00000000..e03f6ced
--- /dev/null
+++ b/app/component/gallery/upload-pic/upload-pic.js
@@ -0,0 +1,26 @@
+'use strict';
+
+require('./_upload-pic.scss');
+module.exports = {
+ template: require('./upload-pic.html'),
+ controller: ['$log', 'picService', UploadPicController],
+ controllerAs: 'uploadPicCtrl',
+ bindings: {
+ gallery: '<'
+ }
+};
+
+function UploadPicController($log, picService){
+ $log.debug('UploadPicController');
+
+ this.pic ={};
+
+ this.uploadPic = function(){
+ picService.uploadGalleryPic(this.gallery, this.pic)
+ .then(() => {
+ this.pic.name = null;
+ this.pic.desc = null;
+ this.pic.file = null;
+ });
+ };
+}
diff --git a/app/component/landing/login/_login.scss b/app/component/landing/login/_login.scss
new file mode 100644
index 00000000..f6771944
--- /dev/null
+++ b/app/component/landing/login/_login.scss
@@ -0,0 +1,20 @@
+.login-form{
+ form{
+ div{
+ margin: 60px auto;
+ input{
+ display: block;
+ width: 80%;
+ height: 30px;
+ margin: 60px auto;
+ border-radius: 10px;
+ text-align: center;
+ }
+
+ }
+ button{
+ margin-left: 20%;
+ height: 30px;
+ }
+ }
+}
diff --git a/app/component/landing/login/login.html b/app/component/landing/login/login.html
new file mode 100644
index 00000000..997b3711
--- /dev/null
+++ b/app/component/landing/login/login.html
@@ -0,0 +1,32 @@
+
+
diff --git a/app/component/landing/login/login.js b/app/component/landing/login/login.js
new file mode 100644
index 00000000..c1e80c2b
--- /dev/null
+++ b/app/component/landing/login/login.js
@@ -0,0 +1,25 @@
+'use strict';
+
+require('./_login.scss');
+module.exports = {
+ template: require('./login.html'),
+ controller: ['$log', '$location', 'authService', LoginController],
+ controllerAs: 'loginCtrl'
+};
+
+function LoginController($log, $location, authService){
+ $log.debug('loginController');
+
+ authService.getToken()
+ .then( () => {
+ $location.url('/home');
+ });
+
+ this.login = function(){
+ $log.debug('loginCtrl.login');
+ authService.login(this.user)
+ .then(() => {
+ $location.url('/home');
+ });
+ };
+}
diff --git a/app/component/landing/signup/_signup.scss b/app/component/landing/signup/_signup.scss
new file mode 100644
index 00000000..eb6bce4f
--- /dev/null
+++ b/app/component/landing/signup/_signup.scss
@@ -0,0 +1,17 @@
+.signup{
+ form{
+ input{
+ display: block;
+ width: 80%;
+ height: 30px;
+ margin: 60px auto;
+ border-radius: 10px;
+ text-align: center;
+ }
+
+ button{
+ margin-left: 20%;
+ height: 30px;
+ }
+ }
+}
diff --git a/app/component/landing/signup/signup.html b/app/component/landing/signup/signup.html
new file mode 100644
index 00000000..aace45a1
--- /dev/null
+++ b/app/component/landing/signup/signup.html
@@ -0,0 +1,9 @@
+
diff --git a/app/component/landing/signup/signup.js b/app/component/landing/signup/signup.js
new file mode 100644
index 00000000..559fc86b
--- /dev/null
+++ b/app/component/landing/signup/signup.js
@@ -0,0 +1,27 @@
+'use strict';
+
+require('./_signup.scss');
+
+module.exports = {
+ template: require('./signup.html'),
+ controller: ['$log', '$location', 'authService', SignUpController],
+ controllerAs: 'signupCtrl'
+};
+
+function SignUpController($log, $location, authService){
+
+
+ authService.getToken()
+ .then( () => {
+ $location.url('/home');
+ });
+
+ this.signup= function(user){
+ $log.debug('SignUpController.signup');
+
+ authService.signup(user)
+ .then(() => {
+ $location.url('/home');
+ });
+ };
+}
diff --git a/app/component/navbar/_navbar.scss b/app/component/navbar/_navbar.scss
new file mode 100644
index 00000000..3057f276
--- /dev/null
+++ b/app/component/navbar/_navbar.scss
@@ -0,0 +1,25 @@
+section{
+ nav{
+ overflow:auto;
+ img{
+ float:left;
+ }
+ h1{
+ float: left;
+ color: #bbb;
+ margin-top: 40px;
+ margin-left: 30px;
+ font-size: 4vw;
+ }
+ div{
+ .btn-nav{
+ padding-left: 5%;
+ padding-right: 5%;
+ float:right;
+ border-radius: 5px;
+ margin-right: 5%;
+ margin-top: 40px;
+ }
+ }
+ }
+}
diff --git a/app/component/navbar/navbar.html b/app/component/navbar/navbar.html
new file mode 100644
index 00000000..90487748
--- /dev/null
+++ b/app/component/navbar/navbar.html
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/component/navbar/navbar.js b/app/component/navbar/navbar.js
new file mode 100644
index 00000000..8bf825b6
--- /dev/null
+++ b/app/component/navbar/navbar.js
@@ -0,0 +1,43 @@
+'use strict';
+
+require('./_navbar.scss');
+
+module.exports = {
+ template: require('./navbar.html'),
+ controller: ['$log', '$location', '$rootScope', 'authService', NavbarController],
+ controllerAs: 'navbarCtrl'
+};
+
+function NavbarController($log, $location, $rootScope, authService) {
+ $log.debug('NavbarController');
+
+ this.checkPath = function() {
+ let path = $location.path();
+ if (path === '/join') {
+ this.hideButtons = true;
+ }
+
+ if (path !== '/join') {
+ this.hideButtons = false;
+ authService.getToken()
+ .catch( () => {
+ $location.url('/join#login');
+ });
+ }
+ };
+
+ this.checkPath();
+
+ $rootScope.$on('$locationChangeSuccess', () => {
+ this.checkPath();
+ });
+
+ this.logout = function() {
+ $log.log('navbarCtrl.logout');
+ this.hideButtons = true;
+ authService.logout()
+ .then( () => {
+ $location.url('/');
+ });
+ };
+}
diff --git a/app/config/log-config.js b/app/config/log-config.js
new file mode 100644
index 00000000..058dcf58
--- /dev/null
+++ b/app/config/log-config.js
@@ -0,0 +1,7 @@
+'use strict';
+
+module.exports = ['$logProvider', logConfig];
+
+function logConfig($logProvider){
+ $logProvider.debugEnabled(__DEBUG__); //eslint-disable-line
+}
diff --git a/app/config/router-config.js b/app/config/router-config.js
new file mode 100644
index 00000000..b1d06a51
--- /dev/null
+++ b/app/config/router-config.js
@@ -0,0 +1,32 @@
+'use strict';
+
+module.exports = ['$stateProvider', '$urlRouterProvider', routerConfig];
+
+function routerConfig($stateProvider, $urlRouterProvider){
+ $urlRouterProvider.when('', '/join#signup');
+ $urlRouterProvider.when('/', '/join#signup');
+ $urlRouterProvider.when('/signup', '/join#signup');
+ $urlRouterProvider.when('/login', '/join#login');
+
+
+ let states = [
+ {
+ name: 'home',
+ url: '/home',
+ template: require('../view/home/home.html'),
+ controller: 'HomeController',
+ controllerAs: 'homeCtrl'
+ },
+ {
+ name: 'landing',
+ url: '/join',
+ template: require('../view/landing/landing.html'),
+ controller: 'LandingController',
+ controllerAs: 'landingCtrl'
+ }
+ ];
+
+ states.forEach(state => {
+ $stateProvider.state(state);
+ });
+}
diff --git a/app/directive/social-icons.html b/app/directive/social-icons.html
new file mode 100644
index 00000000..03630dbf
--- /dev/null
+++ b/app/directive/social-icons.html
@@ -0,0 +1,8 @@
+
diff --git a/app/directive/social-icons.js b/app/directive/social-icons.js
new file mode 100644
index 00000000..efeb7a3e
--- /dev/null
+++ b/app/directive/social-icons.js
@@ -0,0 +1,18 @@
+'use strict';
+
+module.exports = {
+ return {
+ restrict: 'EAC',
+ template: require('./social-icons.html'),
+ controller: ['$log', SocialIconsController],
+ bindToController: true,
+ controllerAs: 'socialIconsCtrl',
+ scope: {
+ tester: '@'
+ }
+ }
+}
+
+function SocialIconsController(){
+ this.icons = ['fb', 'twitter', 'instagram'];
+}
diff --git a/app/entry.js b/app/entry.js
new file mode 100644
index 00000000..8dfe90a1
--- /dev/null
+++ b/app/entry.js
@@ -0,0 +1,49 @@
+'use strict';
+
+require('./scss/main.scss');
+
+const angular = require('angular');
+const pascalcase = require('pascalcase');
+const camelcase = require('camelcase');
+const uiRouter = require('angular-ui-router');
+const ngTouch = require('angular-touch');
+const ngAnimate = require('angular-animate');
+const ngFileUpload = require('ng-file-upload');
+const path = require('path');
+
+const brianGram = angular.module('brianGram',[ngTouch, ngAnimate, uiRouter, ngFileUpload]);
+
+let context = require.context('./config/', true, /\.js$/);
+context.keys().forEach(key => {
+ brianGram.config(context(key));
+});
+
+context = require.context('./view/', true, /\.js$/);
+context.keys().forEach(key => {
+ let name = pascalcase(path.basename(key, '.js'));
+ brianGram.controller(name, context(key));
+});
+
+context = require.context('./service/', true, /\.js$/);
+context.keys().forEach(key => {
+ let name = camelcase(path.basename(key, '.js'));
+ brianGram.service(name, context(key));
+});
+
+context = require.context('./component/', true, /\.js$/);
+context.keys().forEach(key => {
+ let name = camelcase(path.basename(key, '.js'));
+ brianGram.component(name, context(key));
+});
+
+context = require.context('./filter/', true, /\.js$/);
+context.keys().forEach(key => {
+ let name = camelcase(path.basename(key, '.js'));
+ brianGram.filter(name, context(key));
+});
+
+context = require.context('./directive/', true, /\.js$/);
+context.keys().forEach(key => {
+ let name = camelcase(path.basename(key, '.js'));
+ brianGram.directive(name, context(key));
+});
diff --git a/app/filter/gallery-search.js b/app/filter/gallery-search.js
new file mode 100644
index 00000000..11ecef86
--- /dev/null
+++ b/app/filter/gallery-search.js
@@ -0,0 +1,17 @@
+'use strict';
+
+module.exports = function() {
+ return function(galleries, searchTerm) {
+ let fuzzyRegex = generateFuzzyRegex(searchTerm);
+
+ return galleries.filter(gallery => {
+ return fuzzyRegex.test(gallery.name.toUpperCase());
+ });
+ };
+};
+
+function generateFuzzyRegex(input) {
+ if(!input) return /.*/;
+ let fuzzyString = '.*' + input.toUpperCase().split('').join('.*') + '.*';
+ return new RegExp(fuzzyString);
+}
diff --git a/app/index.html b/app/index.html
new file mode 100644
index 00000000..4eae4b46
--- /dev/null
+++ b/app/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+ Brians Gram App
+
+
+
+
+
+
+
+
+
+
diff --git a/app/scss/lib/base/_base.scss b/app/scss/lib/base/_base.scss
new file mode 100644
index 00000000..9148db17
--- /dev/null
+++ b/app/scss/lib/base/_base.scss
@@ -0,0 +1,15 @@
+a{
+ text-decoration: none;
+ color: $white;
+}
+
+.btn-std{
+ padding-left: 30%;
+ padding-right: 30%;
+ border-radius: 10px;
+ background-color: $brand-primary;
+}
+
+body{
+ font-family: helvetica;
+}
diff --git a/app/scss/lib/base/_reset.scss b/app/scss/lib/base/_reset.scss
new file mode 100644
index 00000000..ed11813c
--- /dev/null
+++ b/app/scss/lib/base/_reset.scss
@@ -0,0 +1,48 @@
+/* http://meyerweb.com/eric/tools/css/reset/
+ v2.0 | 20110126
+ License: none (public domain)
+*/
+
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, u, i, center,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, canvas, details, embed,
+figure, figcaption, footer, header, hgroup,
+menu, nav, output, ruby, section, summary,
+time, mark, audio, video {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ font-size: 100%;
+ font: inherit;
+ vertical-align: baseline;
+}
+/* HTML5 display-role reset for older browsers */
+article, aside, details, figcaption, figure,
+footer, header, hgroup, menu, nav, section {
+ display: block;
+}
+body {
+ line-height: 1;
+}
+ol, ul {
+ list-style: none;
+}
+blockquote, q {
+ quotes: none;
+}
+blockquote:before, blockquote:after,
+q:before, q:after {
+ content: '';
+ content: none;
+}
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
diff --git a/app/scss/lib/layout/_content.scss b/app/scss/lib/layout/_content.scss
new file mode 100644
index 00000000..4b97a526
--- /dev/null
+++ b/app/scss/lib/layout/_content.scss
@@ -0,0 +1,6 @@
+main{
+ overflow:auto;
+ width: 100%;
+ background: $brand-secondary;
+ min-height: 720px;
+}
diff --git a/app/scss/lib/layout/_footer.scss b/app/scss/lib/layout/_footer.scss
new file mode 100644
index 00000000..d235a51f
--- /dev/null
+++ b/app/scss/lib/layout/_footer.scss
@@ -0,0 +1,5 @@
+footer{
+ height: 100px;
+ width: 100%;
+ background: $brand-primary;
+}
diff --git a/app/scss/lib/layout/_header.scss b/app/scss/lib/layout/_header.scss
new file mode 100644
index 00000000..55fafc46
--- /dev/null
+++ b/app/scss/lib/layout/_header.scss
@@ -0,0 +1,11 @@
+header{
+ height: 100px;
+ width: 100%;
+ background: $brand-primary;
+ img{
+ height: 50px;
+ width:50px;
+ margin-top: 25px;
+ margin-left: 30px;
+ }
+}
diff --git a/app/scss/lib/theme/_vars.scss b/app/scss/lib/theme/_vars.scss
new file mode 100644
index 00000000..80409a74
--- /dev/null
+++ b/app/scss/lib/theme/_vars.scss
@@ -0,0 +1,5 @@
+$brand-primary: #444;
+$brand-secondary: #888;
+$black: #000;
+$white: #fff;
+$gutter: 60px;
diff --git a/app/scss/main.scss b/app/scss/main.scss
new file mode 100644
index 00000000..20ed08fa
--- /dev/null
+++ b/app/scss/main.scss
@@ -0,0 +1,10 @@
+@import './lib/base/reset';
+@import './lib/theme/vars';
+@import './lib/base/base';
+
+@import './lib/layout/header';
+@import './lib/layout/content';
+@import './lib/layout/footer';
+
+@import '../view/landing/landing';
+@import '../view/home/home';
diff --git a/app/service/auth-service.js b/app/service/auth-service.js
new file mode 100644
index 00000000..973d7da7
--- /dev/null
+++ b/app/service/auth-service.js
@@ -0,0 +1,78 @@
+'use strict';
+
+module.exports = ['$q', '$log', '$http', '$window', authService];
+
+function authService($q, $log, $http, $window){
+ $log.debug('authService');
+ let service = {};
+ let token = null;
+
+ function setToken(_token){
+ $log.debug('authService setToken');
+ if(!_token){
+ return $q.reject(new Error('no token'));
+ }
+ $window.localStorage.setItem('token', _token);
+ token = _token;
+ return $q.resolve(token);
+ }
+
+ service.getToken = function(){
+ $log.debug('authService.getToken');
+ if(token) return $q.resolve(token);
+ token = $window.localStorage.getItem('token');
+ if(token) return $q.resolve(token);
+ return $q.reject(new Error('token not found'));
+ };
+
+ service.signup = function(user){
+ $log.debug('authService.signup');
+ let url = `${__API_URL__}/api/signup`; //eslint-disable-line
+ let config = {
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json'
+ }
+ };
+
+ return $http.post(url, user, config)
+ .then(res => {
+ $log.log('success:', res.data);
+ return setToken(res.data);
+ })
+ .catch(err => {
+ $log.error('failure:', err.message);
+ return $q.reject(err);
+ });
+ };
+
+ service.logout = function(){
+ $log.debug('authService.logout');
+ $window.localStorage.removeItem('token');
+ token = null;
+ return $q.resolve();
+ };
+
+ service.login = function(user){
+ $log.debug('authService.login');
+ let url = `${__API_URL__}/api/login`; //eslint-disable-line
+ let base64 = $window.btoa(`${user.username}:${user.password}`);
+ let config = {
+ headers: {
+ Accept: 'application/json',
+ Authorization: `Basic ${base64}`
+ }
+ };
+
+ return $http.get(url, config)
+ .then(res => {
+ $log.log('success', res.data);
+ return setToken(res.data);
+ })
+ .catch(err => {
+ $log.error(err.message);
+ return $q.reject(err);
+ });
+ };
+ return service;
+}
diff --git a/app/service/gallery-service.js b/app/service/gallery-service.js
new file mode 100644
index 00000000..38e4d465
--- /dev/null
+++ b/app/service/gallery-service.js
@@ -0,0 +1,141 @@
+'use strict';
+
+module.exports = ['$q', '$log', '$http', 'authService', galleryService];
+
+function galleryService($q, $log, $http, authService){
+ $log.debug('galleryService');
+
+ let service = {};
+ service.galleries = [];
+
+ service.createGallery = function(gallery){
+ $log.debug('galleryService.createGallery');
+
+ return authService.getToken()
+ .then(token => {
+ let url = `${__API_URL__}/api/gallery`; //eslint-disable-line
+ let config = {
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${token}`
+ }
+ };
+
+ return $http.post(url, gallery, config);
+ })
+ .then(res => {
+ $log.log('gallery created');
+ let gallery = res.data;
+ service.galleries.unshift(gallery);
+ return gallery;
+ })
+ .catch(err => {
+ $log.error(err.message);
+ });
+ };
+
+ // service.deleteGallery = function(galleryId, galleryData) {
+ // $log.debug('service.deleteGallery');
+ // return authService.getToken()
+ // .then(token => {
+ // let url = `${__API_URL__}/api/gallery/${galleryId}`;
+ // let config = {
+ // headers: {
+ // Accept: 'application/json',
+ // Authorization: `Bearer ${token}`
+ // }
+ // };
+ // });
+ // //TODO finish this route;
+ // };
+
+ service.fetchGalleries = function(){
+ $log.debug('galleryService.fetchGalleries');
+
+ return authService.getToken()
+ .then(token => {
+ let url = `${__API_URL__}/api/gallery`; //eslint-disable-line
+ let config = {
+ headers: {
+ Accept: 'application/json',
+ Authorization: `Bearer ${token}`
+ }
+ };
+
+ return $http.get(url, config);
+ })
+ .then(res => {
+ $log.log('galleries retrieved');
+ service.galleries = res.data;
+ return service.galleries;
+ })
+ .catch(err => {
+ $log.error(err.message);
+ return $q.reject(err);
+ });
+ };
+
+ service.updateGallery = function(galleryId, galleryData){
+ $log.debug('galleryService.updateGallery');
+
+ return authService.getToken()
+ .then(token => {
+ let url = `${__API_URL__}/api/gallery/${galleryId}`; //eslint-disable-line
+ let config = {
+ headers: {
+ Accept: 'application/json',
+ Authorization: `Bearer ${token}`,
+ 'Content-Type': 'application/json'
+ }
+ };
+
+ return $http.put(url, galleryData, config);
+ })
+ .then(res => {
+ $log.log('gallery updated');
+ for(var i = 0; i < service.galleries.length; i++){
+ let current = service.galleries[i];
+ if(current._id === galleryId) {
+ service.galleries[i] = res.data;
+ break;
+ }
+ }
+ return res.data;
+ })
+ .catch(err => {
+ $log.error(err.message);
+ return $q.reject(err);
+ });
+ };
+
+ service.deleteGallery = function(galleryId){
+ $log.debug('galleryService.deleteGallery');
+
+ return authService.getToken()
+ .then(token => {
+ let url = `${__API_URL__}/api/gallery/${galleryId}`; //eslint-disable-line
+ let config = {
+ headers: {
+ Authorization: `Bearer ${token}`
+ }
+ };
+
+ return $http.delete(url, config);
+ })
+ .then(() => {
+ for(let i = 0; i < service.galleries.length; i++) {
+ let current = service.galleries[i];
+ if(current._id === galleryId) {
+ service.galleries.splice(i, 1);
+ }
+ }
+ })
+ .catch(err => {
+ $log.error(err.message);
+ return $q.reject(err);
+ });
+ };
+
+ return service;
+}
diff --git a/app/service/pic-service.js b/app/service/pic-service.js
new file mode 100644
index 00000000..a7e7f49a
--- /dev/null
+++ b/app/service/pic-service.js
@@ -0,0 +1,65 @@
+'use strict';
+
+module.exports = ['$q', '$log', '$http', 'Upload', 'authService', picService];
+
+function picService($q, $log, $http, Upload, authService){
+ $log.debug('picService');
+
+ let service = {};
+ service.uploadGalleryPic = function(galleryData, picData){
+ $log.debug('service.uploadGalleryPic');
+ return authService.getToken()
+ .then(token => {
+ let url = `${__API_URL__}/api/gallery/${galleryData._id}/pic`; //eslint-disable-line
+ let headers = {
+ Authorization: `Bearer ${token}`,
+ Accept: 'application/json'
+ };
+ return Upload.upload({
+ url,
+ headers,
+ method: 'POST',
+ data: {
+ name: picData.name,
+ desc: picData.desc,
+ file: picData.file,
+ }
+ });
+ })
+ .then(res => {
+ galleryData.pics.unshift(res.data);
+ return res.data;
+ })
+ .catch(err => {
+ $log.error(err.message);
+ return $q.reject(err);
+ });
+ };
+
+ service.deleteGalleryPic = function(galleryData, picId){
+ $log.debug('service.deleteGalleryPic');
+ return authService.getToken()
+ .then(token => {
+ let url = `${__API_URL__}/api/gallery/${galleryData._id}/pic/${picId}`; //eslint-disable-line
+ let config = {
+ headers : {
+ Authorization: `Bearer ${token}`
+ }
+ };
+ return $http.delete(url, config);
+ })
+ .then(res => {
+ for(var i = 0; i < galleryData.pics.length; i++){
+ if(galleryData.pics[i]._id == picId.toString()){
+ galleryData.pics.splice(i, 1);
+ }
+ }
+ return res.data;
+ })
+ .catch(err => {
+ $log.error(err.message);
+ return $q.reject(err);
+ });
+ };
+ return service;
+}
diff --git a/app/view/home/_home.scss b/app/view/home/_home.scss
new file mode 100644
index 00000000..6489c6af
--- /dev/null
+++ b/app/view/home/_home.scss
@@ -0,0 +1,7 @@
+.home{
+ position: relative;
+ h2{
+ margin-top: 100px;
+ margin-left: 10%;
+ }
+}
diff --git a/app/view/home/home-controller.js b/app/view/home/home-controller.js
new file mode 100644
index 00000000..66f3833a
--- /dev/null
+++ b/app/view/home/home-controller.js
@@ -0,0 +1,29 @@
+'use strict';
+
+require('./_home.scss');
+
+module.exports = ['$log', '$rootScope', 'galleryService', HomeController];
+
+function HomeController($log, $rootScope, galleryService){
+ $log.debug('Home Controller');
+ this.galleries =[];
+
+ this.fetchGalleries = function(){
+ galleryService.fetchGalleries()
+ .then(galleries => {
+ this.galleries = galleries;
+ this.currentGallery = galleries[0];
+ });
+ };
+
+ this.galleryDeleteDone = function(gallery) {
+ if (this.currentGallery._id === gallery._id) {
+ this.currentGallery = null;
+ }
+ };
+
+ this.fetchGalleries();
+ $rootScope.$on('$locationChangeSuccess', () => {
+ this.fetchGalleries();
+ });
+}
diff --git a/app/view/home/home.html b/app/view/home/home.html
new file mode 100644
index 00000000..ad2b37b1
--- /dev/null
+++ b/app/view/home/home.html
@@ -0,0 +1,17 @@
+
diff --git a/app/view/landing/_landing.scss b/app/view/landing/_landing.scss
new file mode 100644
index 00000000..eef60400
--- /dev/null
+++ b/app/view/landing/_landing.scss
@@ -0,0 +1,16 @@
+.join-container{
+ h2{
+ margin-left: 10%;
+ margin-top: 50px;
+ font-size: 4vw;
+ }
+}
+
+
+.join-inner{
+ p{
+ margin-left: 50%;
+ float:left;
+ margin-right:10px;
+ }
+}
diff --git a/app/view/landing/landing-controller.js b/app/view/landing/landing-controller.js
new file mode 100644
index 00000000..f6c986c5
--- /dev/null
+++ b/app/view/landing/landing-controller.js
@@ -0,0 +1,9 @@
+'use strict';
+
+module.exports = ['$log', '$location', '$rootScope', 'authService', LandingController];
+
+function LandingController($log, $location, authService){ //eslint-disable-line
+ $log.debug('LandingController');
+ let url = $location.url();
+ this.showSignup = url === '/join#signup' || url === '/join';
+}
diff --git a/app/view/landing/landing.html b/app/view/landing/landing.html
new file mode 100644
index 00000000..3033a7e3
--- /dev/null
+++ b/app/view/landing/landing.html
@@ -0,0 +1,27 @@
+
diff --git a/gulpfile.js b/gulpfile.js
new file mode 100644
index 00000000..d66519fb
--- /dev/null
+++ b/gulpfile.js
@@ -0,0 +1,23 @@
+'use strict';
+
+const gulp = require('gulp');
+const eslint = require('gulp-eslint');
+const mocha = require ('gulp-mocha');
+
+gulp.task('test', function(){
+ gulp.src('./test/*-test.js', { read: false})
+ .pipe(mocha({ reporter: 'spec'}));
+});
+
+gulp.task('lint', function() {
+ return gulp.src(['**/*.js', '!node_modules'])
+ .pipe(eslint())
+ .pipe(eslint.format())
+ .pipe(eslint.failAfterError());
+});
+
+gulp.task('dev', function(){
+ gulp.watch(['**/*.js', '!node_modules/**'], ['lint', 'test']);
+});
+
+gulp.task('default', ['dev']);
diff --git a/karma.conf.js b/karma.conf.js
new file mode 100644
index 00000000..79509754
--- /dev/null
+++ b/karma.conf.js
@@ -0,0 +1,75 @@
+// Karma configuration
+// Generated on Tue Apr 04 2017 10:16:51 GMT-0700 (PDT)
+const webpack = require('./webpack.config.js');
+delete webpack.entry;
+
+module.exports = function(config) {
+ config.set({
+ webpack,
+ // base path that will be used to resolve all patterns (eg. files, exclude)
+ basePath: '',
+
+
+ // frameworks to use
+ // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
+ frameworks: ['jasmine'],
+
+
+ // list of files / patterns to load in the browser
+ files: [
+ 'app/entry.js',
+ 'test/**/*-test.js',
+ 'node_modules/angular-mocks/angular-mocks.js'
+ ],
+
+
+ // list of files to exclude
+ exclude: [
+ ],
+
+
+ // preprocess matching files before serving them to the browser
+ // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
+ preprocessors: {
+ 'test/**/*-test.js': ['webpack'],
+ 'app/entry.js': ['webpack']
+ },
+
+
+ // test results reporter to use
+ // possible values: 'dots', 'progress'
+ // available reporters: https://npmjs.org/browse/keyword/karma-reporter
+ reporters: ['mocha'],
+
+
+ // web server port
+ port: 9876,
+
+
+ // enable / disable colors in the output (reporters and logs)
+ colors: true,
+
+
+ // level of logging
+ // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
+ logLevel: config.LOG_INFO,
+
+
+ // enable / disable watching file and executing tests whenever any file changes
+ autoWatch: true,
+
+
+ // start these browsers
+ // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
+ browsers: ['Chrome'],
+
+
+ // Continuous Integration mode
+ // if true, Karma captures browsers, runs the tests and exits
+ singleRun: false,
+
+ // Concurrency level
+ // how many browser should be started simultaneous
+ concurrency: Infinity
+ });
+};
diff --git a/package.json b/package.json
new file mode 100644
index 00000000..b27fc20d
--- /dev/null
+++ b/package.json
@@ -0,0 +1,53 @@
+{
+ "name": "brianGram",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "build": "./node_modules/webpack/bin/webpack.js",
+ "build-watch": "./node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot",
+ "lint": "./node_modules/eslint/bin/eslint.js .",
+ "test": "./node_modules/karma/bin/karma start --single-run",
+ "test-watch": "./node_modules/karma/bin/karma start"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "angular": "^1.6.3",
+ "angular-animate": "^1.6.3",
+ "angular-route": "^1.6.3",
+ "angular-touch": "^1.6.3",
+ "angular-ui-router": "^0.4.2",
+ "babel-core": "^6.24.0",
+ "babel-loader": "^6.4.1",
+ "babel-preset-es2015": "^6.24.0",
+ "camelcase": "^4.0.0",
+ "clean-webpack-plugin": "^0.1.16",
+ "css-loader": "^0.27.3",
+ "dotenv": "^4.0.0",
+ "extract-text-webpack-plugin": "^2.1.0",
+ "file-loader": "^0.10.1",
+ "html-loader": "^0.4.5",
+ "html-webpack-plugin": "^2.28.0",
+ "ng-file-upload": "^12.2.13",
+ "node-sass": "^4.5.1",
+ "pascalcase": "^0.1.1",
+ "resolve-url-loader": "^2.0.2",
+ "sass-loader": "^6.0.3",
+ "style-loader": "^0.16.1",
+ "ui-router": "^1.0.0-alpha.3",
+ "url-loader": "^0.5.8",
+ "webpack": "^2.3.2"
+ },
+ "devDependencies": {
+ "angular-mocks": "^1.6.4",
+ "jasmine-core": "^2.5.2",
+ "karma": "^1.5.0",
+ "karma-chrome-launcher": "^2.0.0",
+ "karma-jasmine": "^1.1.0",
+ "karma-mocha-reporter": "^2.2.3",
+ "karma-webpack": "^2.0.3",
+ "webpack-dev-server": "^2.4.2"
+ }
+}
diff --git a/test/auth-service-test.js b/test/auth-service-test.js
new file mode 100644
index 00000000..e0a0fa0d
--- /dev/null
+++ b/test/auth-service-test.js
@@ -0,0 +1,31 @@
+'use strict';
+
+describe('Auth Service', function(){
+
+ beforeEach(() => {
+ angular.mock.module('brianGram');
+ angular.mock.inject(( $rootScope, authService, $window, $httpBackend) => {
+ this.$window = $window;
+ this.$rootScope = $rootScope;
+ this.authService = authService;
+ this.$httpBackend = $httpBackend;
+ });
+ });
+
+ describe('authService.getToken()', () => {
+ it('should return a token', () => {
+ this.authService.token = null;
+ this.$window.localStorage.setItem('token', 'test token');
+
+ this.authService.getToken()
+ .then( token => {
+ expect(token).toEqual('test token');
+ })
+ .catch( err => {
+ expect(err).toEqual(null);
+ });
+
+ this.$rootScope.$apply();
+ });
+ });
+});
diff --git a/test/edit-gallery-component-test.js b/test/edit-gallery-component-test.js
new file mode 100644
index 00000000..c9b0f186
--- /dev/null
+++ b/test/edit-gallery-component-test.js
@@ -0,0 +1,62 @@
+'use strict';
+
+describe('Edit Gallery Component', function(){
+
+ beforeEach(() => {
+ angular.mock.module('brianGram');
+ angular.mock.inject(($rootScope, $componentController, $httpBackend, authService) => {
+ this.$rootScope = $rootScope;
+ this.$componentController = $componentController;
+ this.$httpBackend = $httpBackend;
+ this.authService = authService;
+ })
+ });
+
+ it('should contain the proper component bindings', () => {
+ let mockBindings = {
+ gallery: {
+ name: 'test gallery name',
+ desc: 'test gallery description'
+ }
+ };
+
+ let editGalleryCtrl = this.$componentController('editGallery', null, mockBindings);
+ expect(editGalleryCtrl.gallery.name).toEqual(mockBindings.gallery.name);
+ expect(editGalleryCtrl.gallery.desc).toEqual(mockBindings.gallery.desc);
+
+ this.$rootScope.$apply();
+ })
+
+ describe('editGalleryCtrl.updateGallery()', () => {
+ it('should make a valid PUT request', () => {
+ let url = 'http://localhost:3000/api/gallery/12345';
+ let headers = {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ Authorization: 'Bearer test token'
+ };
+
+ this.$httpBackend.expectPUT(url, {
+ _id: '12345',
+ name: 'updated name',
+ desc: 'updated description'
+ }, headers).respond(200);
+
+ let mockBindings = {
+ gallery: {
+ _id: '12345',
+ name: 'updated name',
+ desc: 'updated description'
+ },
+ };
+
+ let editGalleryCtrl = this.$componentController('editGallery', null, mockBindings);
+ editGalleryCtrl.gallery.name = 'updated name';
+ editGalleryCtrl.gallery.desc = 'updated description';
+ editGalleryCtrl.updateGallery();
+
+ this.$httpBackend.flush();
+ this.$rootScope.$apply();
+ });
+ });
+});
diff --git a/test/example-test.js b/test/example-test.js
new file mode 100644
index 00000000..c949136b
--- /dev/null
+++ b/test/example-test.js
@@ -0,0 +1,7 @@
+'use strict';
+
+describe('Example Test', function(){
+ it('should pass this test', () => {
+ expect(true).toEqual(true);
+ });
+});
diff --git a/test/gallery-item-component-test.js b/test/gallery-item-component-test.js
new file mode 100644
index 00000000..5f783469
--- /dev/null
+++ b/test/gallery-item-component-test.js
@@ -0,0 +1,63 @@
+'use strict';
+
+describe('Gallery Item Component', function(){
+ beforeEach(() => {
+ angular.mock.module('brianGram');
+ angular.mock.inject(($rootScope, $componentController, $httpBackend, authService) => {
+ this.$rootScope = $rootScope;
+ this.$componentController = $componentController;
+ this.$httpBackend = $httpBackend;
+ this.authService = authService;
+ });
+ });
+
+ describe('galleryItemCtrl.deleteDone()', () => {
+ it('should call deleteDone', () => {
+ let mockBindings = {
+ gallery: {
+ _id: '12345',
+ name: 'test name',
+ desc: 'test description',
+ pics: [],
+ },
+ deleteDone: function(data){
+ expect(data.galleryData._id).toEqual('12345');
+ }
+ };
+
+ let galleryItemCtrl = this.$componentController('galleryItem', null, mockBindings);
+ galleryItemCtrl.deleteDone({galleryData: galleryItemCtrl.gallery});
+
+ this.$rootScope.$apply();
+ });
+ });
+
+ it('should call deleteDone with gallery after galleryDelete', () => {
+ let url = 'http://localhost:3000/api/gallery/12345';
+ let headers = {
+ Authorization: 'Bearer test token',
+ Accept: 'application/json, text/plain, */*'
+ };
+
+ let mockBindings = {
+ gallery: {
+ _id: '12345',
+ name: 'test name',
+ desc: 'test description',
+ pics: []
+ },
+ deleteDone: function(data){
+ expect(data.galleryData._id).toEqual(mockBindings.gallery._id);
+ expect(data.galleryData.name).toEqual(mockBindings.gallery.name);
+ expect(data.galleryData.desc).toEqual(mockBindings.gallery.desc);
+ }
+ };
+
+ this.$httpBackend.expectDELETE(url, headers).respond(204);
+
+ let galleryItemCtrl = this.$componentController('galleryItem', null, mockBindings);
+ galleryItemCtrl.deleteGallery();
+ this.$httpBackend.flush();
+ this.$rootScope.$apply();
+ });
+});
diff --git a/test/gallery-service-test.js b/test/gallery-service-test.js
new file mode 100644
index 00000000..4e5c0483
--- /dev/null
+++ b/test/gallery-service-test.js
@@ -0,0 +1,58 @@
+'use strict';
+
+describe('Gallery Service', function() {
+
+ beforeEach(() => {
+ angular.mock.module('brianGram');
+ angular.mock.inject(( $rootScope, authService, galleryService, $window, $httpBackend) => {
+ this.$window = $window;
+ this.$rootScope = $rootScope;
+ this.authService = authService;
+ this.galleryService = galleryService;
+ this.$httpBackend = $httpBackend;
+ });
+ });
+
+ describe('galleryService.createGallery()', () => {
+ it('should create a new gallery', () => {
+ let galleryData = {
+ name: 'example gallery',
+ desc: 'example description'
+ };
+
+ let headers = {
+ 'Content-Type': 'application/json',
+ Accept: 'application/json',
+ Authorization: 'Bearer test token'
+ };
+
+ this.$httpBackend.expectPOST('http://localhost:3000/api/gallery', galleryData, headers)
+ .respond(200, {
+ _id: '1234',
+ username: 'testuser',
+ name: galleryData.name,
+ desc: galleryData.desc,
+ pics: []
+ });
+
+ this.galleryService.createGallery(galleryData);
+ this.$httpBackend.flush();
+ this.$rootScope.$apply();
+ });
+ });
+
+ describe('galleryService.deleteGallery()', () => {
+ it('should delete a gallery', () => {
+ let galleryID = 'testid';
+ let headers = {
+ Authorization: 'Bearer test token',
+ Accept: "application/json, text/plain, */*",
+ };
+
+ this.$httpBackend.expectDELETE('http://localhost:3000/api/gallery/testid', headers).respond(204);
+ this.galleryService.deleteGallery(galleryID);
+ this.$httpBackend.flush();
+ this.$rootScope.$apply();
+ })
+ });
+});
diff --git a/webpack.config.js b/webpack.config.js
new file mode 100644
index 00000000..0f235540
--- /dev/null
+++ b/webpack.config.js
@@ -0,0 +1,47 @@
+'use strict';
+
+const dotenv = require('dotenv');
+
+const HTMLPlugin = require('html-webpack-plugin');
+const ExtractTextPlugin = require('extract-text-webpack-plugin');
+const webpack = require('webpack');
+
+const production = process.env.NODE_ENV === 'production';
+
+dotenv.load();
+
+module.exports = {
+ devtool: 'eval',
+ entry: `${__dirname}/app/entry.js`,
+ output: {
+ filename: 'bundle.js',
+ path: `${__dirname}/build`
+ },
+ plugins: [
+ new HTMLPlugin({
+ template: `${__dirname}/app/index.html`
+ }),
+ new ExtractTextPlugin('bundle.css'),
+ new webpack.DefinePlugin({
+ __API_URL__: JSON.stringify(process.env.API_URL),
+ __DEBUG__: JSON.stringify(!production)
+ })
+ ],
+ module: {
+ rules: [
+ {
+ test: /\.js$/,
+ exclude: /node_modules/,
+ loader: 'babel-loader'
+ },
+ {
+ test: /\.html$/,
+ loader: 'html-loader'
+ },
+ {
+ test: /\.scss$/,
+ loader: ExtractTextPlugin.extract(['css-loader', 'sass-loader'])
+ }
+ ]
+ }
+};