diff --git a/lab-brae/.babelrc b/lab-brae/.babelrc
new file mode 100644
index 00000000..af0f0c3d
--- /dev/null
+++ b/lab-brae/.babelrc
@@ -0,0 +1,3 @@
+{
+ "presets": ["es2015"]
+}
\ No newline at end of file
diff --git a/lab-brae/.eslintrc b/lab-brae/.eslintrc
new file mode 100644
index 00000000..b663d772
--- /dev/null
+++ b/lab-brae/.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"
+}
\ No newline at end of file
diff --git a/lab-brae/.gitignore b/lab-brae/.gitignore
new file mode 100644
index 00000000..9cff7ab9
--- /dev/null
+++ b/lab-brae/.gitignore
@@ -0,0 +1,131 @@
+
+# Created by https://www.gitignore.io/api/node,vim,macos,linux,windows
+
+node_modules/
+
+### Node ###
+# Logs
+logs
+*.log
+npm-debug.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
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules
+jspm_packages
+
+# 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
+
+.env
+
+### Vim ###
+# swap
+[._]*.s[a-w][a-z]
+[._]s[a-w][a-z]
+# session
+Session.vim
+# temporary
+.netrwhist
+*~
+# auto-generated tag files
+tags
+
+
+### macOS ###
+*.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
+
+
+### 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*
+
+
+### Windows ###
+# Windows image file caches
+Thumbs.db
+ehthumbs.db
+
+# Folder config file
+Desktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+*.env
+build
\ No newline at end of file
diff --git a/lab-brae/app/component/gallery/create-gallery/_create-gallery.scss b/lab-brae/app/component/gallery/create-gallery/_create-gallery.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/lab-brae/app/component/gallery/create-gallery/create-gallery.html b/lab-brae/app/component/gallery/create-gallery/create-gallery.html
new file mode 100644
index 00000000..dd4dc8b7
--- /dev/null
+++ b/lab-brae/app/component/gallery/create-gallery/create-gallery.html
@@ -0,0 +1,19 @@
+
diff --git a/lab-brae/app/component/gallery/create-gallery/create-gallery.js b/lab-brae/app/component/gallery/create-gallery/create-gallery.js
new file mode 100644
index 00000000..9057808a
--- /dev/null
+++ b/lab-brae/app/component/gallery/create-gallery/create-gallery.js
@@ -0,0 +1,21 @@
+'use strict';
+
+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;
+ });
+ };
+};
\ No newline at end of file
diff --git a/lab-brae/app/component/gallery/edit-gallery/_edit-gallery.scss b/lab-brae/app/component/gallery/edit-gallery/_edit-gallery.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/lab-brae/app/component/gallery/edit-gallery/edit-gallery.html b/lab-brae/app/component/gallery/edit-gallery/edit-gallery.html
new file mode 100644
index 00000000..5032fe3f
--- /dev/null
+++ b/lab-brae/app/component/gallery/edit-gallery/edit-gallery.html
@@ -0,0 +1,21 @@
+
diff --git a/lab-brae/app/component/gallery/edit-gallery/edit-gallery.js b/lab-brae/app/component/gallery/edit-gallery/edit-gallery.js
new file mode 100644
index 00000000..623cdfec
--- /dev/null
+++ b/lab-brae/app/component/gallery/edit-gallery/edit-gallery.js
@@ -0,0 +1,22 @@
+'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);
+ };
+};
\ No newline at end of file
diff --git a/lab-brae/app/component/gallery/gallery-item/_gallery-item.scss b/lab-brae/app/component/gallery/gallery-item/_gallery-item.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/lab-brae/app/component/gallery/gallery-item/gallery-item.html b/lab-brae/app/component/gallery/gallery-item/gallery-item.html
new file mode 100644
index 00000000..9903fbb2
--- /dev/null
+++ b/lab-brae/app/component/gallery/gallery-item/gallery-item.html
@@ -0,0 +1,23 @@
+
+
+
+ name:
+ {{ galleryItemCtrl.gallery.name }}
+
+
+
+ description:
+ {{ galleryItemCtrl.gallery.desc }}
+
+
+
+
+
+
+ edit
+
+
+
+
+ delete
+
\ No newline at end of file
diff --git a/lab-brae/app/component/gallery/gallery-item/gallery-item.js b/lab-brae/app/component/gallery/gallery-item/gallery-item.js
new file mode 100644
index 00000000..2768da14
--- /dev/null
+++ b/lab-brae/app/component/gallery/gallery-item/gallery-item.js
@@ -0,0 +1,22 @@
+'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);
+ };
+};
\ No newline at end of file
diff --git a/lab-brae/app/component/gallery/thumbnail-container/_thumbnail-container.scss b/lab-brae/app/component/gallery/thumbnail-container/_thumbnail-container.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/lab-brae/app/component/gallery/thumbnail-container/thumbnail-container.html b/lab-brae/app/component/gallery/thumbnail-container/thumbnail-container.html
new file mode 100644
index 00000000..6ea1fe6f
--- /dev/null
+++ b/lab-brae/app/component/gallery/thumbnail-container/thumbnail-container.html
@@ -0,0 +1,8 @@
+
+
{{ thumbnailContainerCtrl.gallery.name }}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/lab-brae/app/component/gallery/thumbnail-container/thumbnail-container.js b/lab-brae/app/component/gallery/thumbnail-container/thumbnail-container.js
new file mode 100644
index 00000000..68b89a02
--- /dev/null
+++ b/lab-brae/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: '<'
+ }
+};
\ No newline at end of file
diff --git a/lab-brae/app/component/gallery/thumbnail/_thumbnail.scss b/lab-brae/app/component/gallery/thumbnail/_thumbnail.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/lab-brae/app/component/gallery/thumbnail/thumbnail.html b/lab-brae/app/component/gallery/thumbnail/thumbnail.html
new file mode 100644
index 00000000..88765c43
--- /dev/null
+++ b/lab-brae/app/component/gallery/thumbnail/thumbnail.html
@@ -0,0 +1,10 @@
+
+
+
+
+ delete
+
+
+
\ No newline at end of file
diff --git a/lab-brae/app/component/gallery/thumbnail/thumbnail.js b/lab-brae/app/component/gallery/thumbnail/thumbnail.js
new file mode 100644
index 00000000..9f4c8c25
--- /dev/null
+++ b/lab-brae/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);
+ };
+};
\ No newline at end of file
diff --git a/lab-brae/app/component/gallery/upload-pic/_upload-pic.scss b/lab-brae/app/component/gallery/upload-pic/_upload-pic.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/lab-brae/app/component/gallery/upload-pic/upload-pic.html b/lab-brae/app/component/gallery/upload-pic/upload-pic.html
new file mode 100644
index 00000000..65cb4c09
--- /dev/null
+++ b/lab-brae/app/component/gallery/upload-pic/upload-pic.html
@@ -0,0 +1,23 @@
+
\ No newline at end of file
diff --git a/lab-brae/app/component/gallery/upload-pic/upload-pic.js b/lab-brae/app/component/gallery/upload-pic/upload-pic.js
new file mode 100644
index 00000000..0321d3fe
--- /dev/null
+++ b/lab-brae/app/component/gallery/upload-pic/upload-pic.js
@@ -0,0 +1,27 @@
+'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;
+ });
+ };
+};
\ No newline at end of file
diff --git a/lab-brae/app/component/landing/login/_login.scss b/lab-brae/app/component/landing/login/_login.scss
new file mode 100644
index 00000000..8d08f751
--- /dev/null
+++ b/lab-brae/app/component/landing/login/_login.scss
@@ -0,0 +1,13 @@
+@import "../../../scss/lib/theme/vars";
+
+.signup {
+ .input-std {
+ margin-bottom: 2.5%;
+ }
+
+ button {
+ float: right;
+ padding-top: 1.5vw;
+ padding-bottom: 1.5vw;
+ }
+}
diff --git a/lab-brae/app/component/landing/login/login.html b/lab-brae/app/component/landing/login/login.html
new file mode 100644
index 00000000..16a3f798
--- /dev/null
+++ b/lab-brae/app/component/landing/login/login.html
@@ -0,0 +1,31 @@
+
\ No newline at end of file
diff --git a/lab-brae/app/component/landing/login/login.js b/lab-brae/app/component/landing/login/login.js
new file mode 100644
index 00000000..5451929e
--- /dev/null
+++ b/lab-brae/app/component/landing/login/login.js
@@ -0,0 +1,27 @@
+'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');
+ });
+ };
+};
\ No newline at end of file
diff --git a/lab-brae/app/component/landing/signup/_signup.scss b/lab-brae/app/component/landing/signup/_signup.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/lab-brae/app/component/landing/signup/signup.html b/lab-brae/app/component/landing/signup/signup.html
new file mode 100644
index 00000000..f1b9f38d
--- /dev/null
+++ b/lab-brae/app/component/landing/signup/signup.html
@@ -0,0 +1,25 @@
+
\ No newline at end of file
diff --git a/lab-brae/app/component/landing/signup/signup.js b/lab-brae/app/component/landing/signup/signup.js
new file mode 100644
index 00000000..b5923714
--- /dev/null
+++ b/lab-brae/app/component/landing/signup/signup.js
@@ -0,0 +1,25 @@
+'use strict';
+
+module.exports = {
+ template: require('./signup.html'),
+ controller: ['$log', '$location', 'authService', SignupController],
+ controllerAs: 'signupCtrl'
+};
+
+function SignupController($log, $location, authService) {
+ $log.debug('SignupController');
+
+ authService.getToken()
+ .then( () => {
+ $location.url('/home');
+ });
+
+ this.signup = function(user) {
+ $log.debug('signupCtrl.signup');
+
+ authService.signup(user)
+ .then( () => {
+ $location.url('/home')
+ });
+ };
+};
diff --git a/lab-brae/app/component/navbar/_navbar.scss b/lab-brae/app/component/navbar/_navbar.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/lab-brae/app/component/navbar/navbar.html b/lab-brae/app/component/navbar/navbar.html
new file mode 100644
index 00000000..dbe10a00
--- /dev/null
+++ b/lab-brae/app/component/navbar/navbar.html
@@ -0,0 +1,9 @@
+
+ cfgram
+
+
+
diff --git a/lab-brae/app/component/navbar/navbar.js b/lab-brae/app/component/navbar/navbar.js
new file mode 100644
index 00000000..3f4fa068
--- /dev/null
+++ b/lab-brae/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/lab-brae/app/config/log-config.js b/lab-brae/app/config/log-config.js
new file mode 100644
index 00000000..c84d58c8
--- /dev/null
+++ b/lab-brae/app/config/log-config.js
@@ -0,0 +1,7 @@
+'use strict';
+
+module.exports = ['$logProvider', logConfig];
+
+function logConfig($logProvider) {
+ $logProvider.debugEnabled(__DEBUG__);
+};
diff --git a/lab-brae/app/config/router-config.js b/lab-brae/app/config/router-config.js
new file mode 100644
index 00000000..5357c24b
--- /dev/null
+++ b/lab-brae/app/config/router-config.js
@@ -0,0 +1,31 @@
+'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/lab-brae/app/directive/_social-icons.scss b/lab-brae/app/directive/_social-icons.scss
new file mode 100644
index 00000000..a40063be
--- /dev/null
+++ b/lab-brae/app/directive/_social-icons.scss
@@ -0,0 +1,19 @@
+
+
+.social-icon-facebook {
+ background: url('https://s3-us-west-2.amazonaws.com/slugtestbucket/social-icons-sprite-sheet.png') no-repeat -36px -27px;
+ width: 128px;
+ height: 128px;
+ }
+
+ .social-icon-twitter {
+ background: url('https://s3-us-west-2.amazonaws.com/slugtestbucket/social-icons-sprite-sheet.png') no-repeat -436px -27px;
+ width: 128px;
+ height: 128px;
+ }
+
+ .social-icon-instagram {
+ background: url('https://s3-us-west-2.amazonaws.com/slugtestbucket/social-icons-sprite-sheet.png') no-repeat -636px -27px;
+ width: 128px;
+ height: 128px;
+ }
diff --git a/lab-brae/app/directive/social-icons.html b/lab-brae/app/directive/social-icons.html
new file mode 100644
index 00000000..73190466
--- /dev/null
+++ b/lab-brae/app/directive/social-icons.html
@@ -0,0 +1,7 @@
+
diff --git a/lab-brae/app/directive/social-icons.js b/lab-brae/app/directive/social-icons.js
new file mode 100644
index 00000000..c76ddc87
--- /dev/null
+++ b/lab-brae/app/directive/social-icons.js
@@ -0,0 +1,21 @@
+'use strict';
+
+require('./_social-icons.scss')
+
+module.exports = function() {
+ return {
+ restrict: 'EAC',
+ template: require('./social-icons.html'),
+ controller: ['$log', SocialIconsController],
+ bindToController: true,
+ controllerAs: 'socialIconsCtrl',
+ scope: {
+ tester: '@'
+ }
+ };
+};
+
+function SocialIconsController() {
+ this.icons = ['facebook', 'twitter', 'instagram'];
+};
+
diff --git a/lab-brae/app/entry.js b/lab-brae/app/entry.js
new file mode 100644
index 00000000..b48b3553
--- /dev/null
+++ b/lab-brae/app/entry.js
@@ -0,0 +1,54 @@
+'use strict';
+
+require('./scss/main.scss');
+
+const path = require('path');
+const angular = require('angular');
+const camelcase = require('camelcase');
+const pascalcase = require('pascalcase');
+const uiRouter = require('angular-ui-router');
+const ngTouch = require('angular-touch');
+const ngAnimate = require('angular-animate');
+const ngFileUpload = require('ng-file-upload');
+
+const cfgram = angular.module('cfgram', [ngTouch, ngAnimate, uiRouter, ngFileUpload]);
+
+let context = require.context('./config/', true, /\.js$/);
+context.keys().forEach( key => {
+ cfgram.config(context(key));
+});
+
+context = require.context('./view/', true, /\.js$/);
+context.keys().forEach( key => {
+ let name = pascalcase(path.basename(key, '.js'));
+ let module = context(key);
+ cfgram.controller(name, module);
+});
+
+context = require.context('./service/', true, /\.js$/);
+context.keys().forEach( key => {
+ let name = camelcase(path.basename(key, '.js'));
+ let module = context(key);
+ cfgram.service(name, module);
+});
+
+context = require.context('./component/', true, /\.js$/);
+context.keys().forEach( key => {
+ let name = camelcase(path.basename(key, '.js'));
+ let module = context(key);
+ cfgram.component(name, module);
+});
+
+context = require.context('./filter/', true, /\.js$/);
+context.keys().forEach( key => {
+ let name = camelcase(path.basename(key, '.js'));
+ let module = context(key);
+ cfgram.filter(name, module);
+});
+
+context = require.context('./directive/', true, /\.js$/);
+context.keys().forEach( key => {
+ let name = camelcase(path.basename(key, '.js'));
+ let module = context(key);
+ cfgram.directive(name, module);
+});
\ No newline at end of file
diff --git a/lab-brae/app/filter/gallery-search.js b/lab-brae/app/filter/gallery-search.js
new file mode 100644
index 00000000..66a89f5f
--- /dev/null
+++ b/lab-brae/app/filter/gallery-search.js
@@ -0,0 +1,17 @@
+'use strict';
+
+module.exports = function() {
+ return function(gallery, searchTerm) {
+ let fuzzyRegex = generateFuzzyRegex(searchTerm);
+
+ return gallery.filter( gallery => {
+ return fuzzyRegex.test(gallery.name.toUpperCase());
+ });
+ };
+};
+
+function generateFuzzyRegex(input) {
+ if (!input) return /.*/;
+ let fuzzyString = '.*' + input.toUpperCase().split('').join('.*') + '.*';
+ return new RegExp(fuzzyString);
+};
\ No newline at end of file
diff --git a/lab-brae/app/index.html b/lab-brae/app/index.html
new file mode 100644
index 00000000..b1ac4d73
--- /dev/null
+++ b/lab-brae/app/index.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+ CFgram
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lab-brae/app/scss/lib/base/_base.scss b/lab-brae/app/scss/lib/base/_base.scss
new file mode 100644
index 00000000..01b77d55
--- /dev/null
+++ b/lab-brae/app/scss/lib/base/_base.scss
@@ -0,0 +1,50 @@
+@import '../theme/vars';
+
+html, body {
+ width: 100%;
+ height: 100%;
+}
+
+button {
+ border: none;
+ border-radius: 3px;
+ background: $brand-secondary;
+ color: $white;
+ padding: 1% 5%;
+ cursor: pointer;
+}
+
+a {
+ text-decoration: none;
+}
+
+input[type="text"] {
+ width: 100%;
+ padding: 1.5vw 2vw;
+ font-size: 2vw;
+ border: solid 1px $brand-primary;
+ border-radius: 3px;
+ box-sizing: border-box;
+
+ a:focus {
+ background: $brand-primary;
+ color: $white;
+ }
+}
+
+input[type="password"] {
+ @extend input[type="text"];
+}
+
+input[disabled="disabled"] {
+ background: #e3e3e3;
+}
+
+.input-std {
+ @extend input[type="text"];
+ display: inline-block;
+}
+
+fieldset {
+ margin: 2% 0;
+}
\ No newline at end of file
diff --git a/lab-brae/app/scss/lib/base/_reset.scss b/lab-brae/app/scss/lib/base/_reset.scss
new file mode 100644
index 00000000..af944401
--- /dev/null
+++ b/lab-brae/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;
+}
\ No newline at end of file
diff --git a/lab-brae/app/scss/lib/layout/_content.scss b/lab-brae/app/scss/lib/layout/_content.scss
new file mode 100644
index 00000000..ebb3b596
--- /dev/null
+++ b/lab-brae/app/scss/lib/layout/_content.scss
@@ -0,0 +1,8 @@
+@import '../theme/vars';
+
+main {
+ background: $brand-primary;
+ width: 90%;
+ margin: 5%;
+ min-height: 600px;
+}
\ No newline at end of file
diff --git a/lab-brae/app/scss/lib/layout/_footer.scss b/lab-brae/app/scss/lib/layout/_footer.scss
new file mode 100644
index 00000000..59dfe795
--- /dev/null
+++ b/lab-brae/app/scss/lib/layout/_footer.scss
@@ -0,0 +1,32 @@
+@import '../theme/vars';
+
+footer {
+ background: $brand-tertiary;
+ font-family: Helvetica;
+ color: $white;
+ height: 8vw;
+ margin-top: $gutter-std * 2;
+}
+
+.social-icon {
+ li {
+ .sprite-facebook {
+ background: url('https://s3-us-west-2.amazonaws.com/slugtestbucket/social-icons-sprite-sheet.png') no-repeat -36px -27px;
+ width: 128px;
+ height: 128px;
+ }
+
+
+ .sprite-twitter {
+ background: url('https://s3-us-west-2.amazonaws.com/slugtestbucket/social-icons-sprite-sheet.png') no-repeat -436px -27px;
+ width: 128px;
+ height: 128px;
+ }
+
+ .sprite-instagram {
+ background: url('https://s3-us-west-2.amazonaws.com/slugtestbucket/social-icons-sprite-sheet.png') no-repeat -636px -27px;
+ width: 128px;
+ height: 128px;
+ }
+ }
+}
\ No newline at end of file
diff --git a/lab-brae/app/scss/lib/layout/_header.scss b/lab-brae/app/scss/lib/layout/_header.scss
new file mode 100644
index 00000000..f06ea953
--- /dev/null
+++ b/lab-brae/app/scss/lib/layout/_header.scss
@@ -0,0 +1,13 @@
+@import '../theme/vars';
+
+header {
+ background: $brand-secondary;
+ font-family: Helvetica;
+ color: $white;
+ height: 8vw;
+
+ h2 {
+ font-size: 4vw;
+ }
+
+}
\ No newline at end of file
diff --git a/lab-brae/app/scss/lib/theme/_vars.scss b/lab-brae/app/scss/lib/theme/_vars.scss
new file mode 100644
index 00000000..5f2fc6a7
--- /dev/null
+++ b/lab-brae/app/scss/lib/theme/_vars.scss
@@ -0,0 +1,7 @@
+$font-family: Helvetica;
+$white: #fff;
+$black: #000;
+$brand-primary: #d7d7d7;
+$brand-secondary: #494949;
+$brand-tertiary: #9b9b9b;
+$gutter-std: 5%;
\ No newline at end of file
diff --git a/lab-brae/app/scss/main.scss b/lab-brae/app/scss/main.scss
new file mode 100644
index 00000000..d01d80b9
--- /dev/null
+++ b/lab-brae/app/scss/main.scss
@@ -0,0 +1,27 @@
+// :: BASE :: //
+@import './lib/base/reset';
+@import './lib/base/base';
+
+// :: LAYOUT :: //
+@import './lib/layout/content';
+@import './lib/layout/footer';
+@import './lib/layout/header';
+
+// :: VIEWS :: //
+@import '/../view/home/home';
+@import '/../view/landing/landing';
+
+// :: COMPONENTS :: //
+@import '/../component/navbar/navbar';
+@import '/../component/landing/login/login';
+@import '/../component/landing/signup/signup';
+@import '/../component/gallery/create-gallery/create-gallery';
+@import '/../component/gallery/edit-gallery/edit-gallery';
+@import '/../component/gallery/gallery-item/gallery-item';
+@import '/../component/gallery/thumbnail-container/thumbnail-container';
+@import '/../component/gallery/thumbnail/thumbnail';
+@import '/../component/gallery/upload-pic/upload-pic';
+@import '/../directive/social-icons';
+
+
+
diff --git a/lab-brae/app/service/auth-service.js b/lab-brae/app/service/auth-service.js
new file mode 100644
index 00000000..2e404f26
--- /dev/null
+++ b/lab-brae/app/service/auth-service.js
@@ -0,0 +1,88 @@
+'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`;
+ 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`;
+ 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/lab-brae/app/service/gallery-service.js b/lab-brae/app/service/gallery-service.js
new file mode 100644
index 00000000..06376e34
--- /dev/null
+++ b/lab-brae/app/service/gallery-service.js
@@ -0,0 +1,145 @@
+'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`;
+ 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);
+ return $q.reject(err);
+ });
+ };
+
+ service.deleteGalleries = function(galleryID) {
+ $log.debug('galleryService.deleteGalleries');
+
+ return authService.getToken()
+ .then( token => {
+ let url = `${__API_URL__}/api/gallery/${galleryID}`;
+ let config = {
+ headers: {
+ Accept: 'application/json',
+ Authorization: `Bearer ${token}`
+ }
+ };
+
+ // CREATE $HTTP.DELETE REQUEST HERE
+ });
+ };
+
+ service.fetchGalleries = function() {
+ $log.debug('galleryService.fetchGalleries');
+
+ return authService.getToken()
+ .then( token => {
+ let url = `${__API_URL__}/api/gallery`;
+ 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}`;
+ let config = {
+ headers: {
+ Accept: 'application/json',
+ Authorization: `Bearer ${token}`,
+ 'Content-Type': 'application/json'
+ }
+ };
+
+ return $http.put(url, galleryData, config);
+ })
+ .then( res => {
+ for (let 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}`;
+ let config = {
+ headers: {
+ Authorization: `Bearer ${token}`
+ }
+ };
+
+ return $http.delete(url, config);
+ })
+ .then( res => {
+ for (let i=0; i < service.galleries.length; i++) {
+ let current = service.galleries[i];
+ if(current._id === galleryID) {
+ service.galleries.splice(i, 1);
+ break;
+ }
+ }
+ })
+ .catch( err => {
+ $log.error(err.message);
+ return $q.reject(err);
+ });
+ };
+
+ return service;
+};
\ No newline at end of file
diff --git a/lab-brae/app/service/pic-service.js b/lab-brae/app/service/pic-service.js
new file mode 100644
index 00000000..0d736938
--- /dev/null
+++ b/lab-brae/app/service/pic-service.js
@@ -0,0 +1,74 @@
+'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`;
+ 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, picData) {
+ $log.debug('picService.deleteGalleryPic');
+
+ return authService.getToken()
+ .then( token => {
+ let url = `${__API_URL__}/api/gallery/${galleryData._id}/pic/${picData._id}`;
+ let config = {
+ headers: {
+ Authorization: `Bearer ${token}`
+ }
+ };
+
+ return $http.delete(url, config);
+ })
+ .then( res => {
+ $log.log(res);
+
+ for (let i = 0; i < galleryData.pics.length; i++) {
+ let current = galleryData.pics[i];
+ if (current._id === picData._id) {
+ galleryData.pics.splice(i, 1);
+ break;
+ }
+ }
+ })
+ .catch( err => {
+ $log.error(err.message);
+ return $q.reject(err);
+ });
+ };
+
+ return service;
+};
\ No newline at end of file
diff --git a/lab-brae/app/view/home/_home.scss b/lab-brae/app/view/home/_home.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/lab-brae/app/view/home/home-controller.js b/lab-brae/app/view/home/home-controller.js
new file mode 100644
index 00000000..24cacec9
--- /dev/null
+++ b/lab-brae/app/view/home/home-controller.js
@@ -0,0 +1,31 @@
+'use strict';
+
+require('./_home.scss');
+
+module.exports = ['$log', '$rootScope', 'galleryService', HomeController];
+
+function HomeController($log, $rootScope, galleryService) {
+ $log.debug('HomeController');
+
+ 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/lab-brae/app/view/home/home.html b/lab-brae/app/view/home/home.html
new file mode 100644
index 00000000..ced10903
--- /dev/null
+++ b/lab-brae/app/view/home/home.html
@@ -0,0 +1,18 @@
+
diff --git a/lab-brae/app/view/landing/_landing.scss b/lab-brae/app/view/landing/_landing.scss
new file mode 100644
index 00000000..32bd1dfb
--- /dev/null
+++ b/lab-brae/app/view/landing/_landing.scss
@@ -0,0 +1,17 @@
+@import "../../scss/lib/theme/vars";
+
+.join-inner {
+ div:last-child {
+ margin-top: 1.75vw;
+ text-align: right;
+ font-size: 2vw;
+ p {
+ display: inline;
+ margin-right: 1vw;
+ }
+
+ a {
+ margin-right: 1.5vw;
+ }
+ }
+}
\ No newline at end of file
diff --git a/lab-brae/app/view/landing/landing-controller.js b/lab-brae/app/view/landing/landing-controller.js
new file mode 100644
index 00000000..ac3f9342
--- /dev/null
+++ b/lab-brae/app/view/landing/landing-controller.js
@@ -0,0 +1,10 @@
+'use strict';
+
+require('./_landing.scss');
+
+module.exports = ['$log', '$location', '$rootScope', 'authService', LandingController];
+
+function LandingController($log, $location, authService) {
+ let url = $location.url();
+ this.showSignup = url === '/join#signup' || url === '/join';
+};
\ No newline at end of file
diff --git a/lab-brae/app/view/landing/landing.html b/lab-brae/app/view/landing/landing.html
new file mode 100644
index 00000000..bcd51cc4
--- /dev/null
+++ b/lab-brae/app/view/landing/landing.html
@@ -0,0 +1,25 @@
+
diff --git a/lab-brae/karma.conf.js b/lab-brae/karma.conf.js
new file mode 100644
index 00000000..cfa0bbfe
--- /dev/null
+++ b/lab-brae/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/lab-brae/package.json b/lab-brae/package.json
new file mode 100644
index 00000000..d882a673
--- /dev/null
+++ b/lab-brae/package.json
@@ -0,0 +1,52 @@
+{
+ "name": "lab-brae",
+ "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",
+ "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/lab-brae/test/auth-service-test.js b/lab-brae/test/auth-service-test.js
new file mode 100644
index 00000000..9e10f162
--- /dev/null
+++ b/lab-brae/test/auth-service-test.js
@@ -0,0 +1,32 @@
+'use strict';
+
+describe('Auth Service', function() {
+
+ beforeEach(() => {
+ angular.mock.module('cfgram');
+ 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();
+
+ });
+ });
+});
\ No newline at end of file
diff --git a/lab-brae/test/create-gallery-component-test.js b/lab-brae/test/create-gallery-component-test.js
new file mode 100644
index 00000000..5822ca22
--- /dev/null
+++ b/lab-brae/test/create-gallery-component-test.js
@@ -0,0 +1,41 @@
+'use strict';
+
+describe('Create Gallery Component', function(){
+ beforeEach( () => {
+ angular.mock.module('cfgram');
+ angular.mock.inject(($rootScope, $componentController, $httpBackend, authService) => {
+ this.$rootScope = $rootScope;
+ this.$componentController = $componentController;
+ this.$httpBackend = $httpBackend;
+ this.authService = authService;
+ });
+ });
+
+ describe('createGalleryCtrl.createGallery()', () => {
+ it('should make a valid POST request', () => {
+ let url = `${__API_URL__}/api/gallery`;
+ let exampleGallery = {
+ name: 'test name',
+ desc: 'test description'
+ };
+
+ let headers = {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ Authorization: 'Bearer test token'
+ };
+
+
+ this.$httpBackend.expectPOST(url, exampleGallery, headers).respond(200);
+
+ let createGalleryCtrl = this.$componentController('createGallery', null);
+
+ createGalleryCtrl.gallery = exampleGallery;
+ expect(createGalleryCtrl.gallery.name).toEqual(exampleGallery.name);
+ expect(createGalleryCtrl.gallery.desc).toEqual(exampleGallery.desc);
+ createGalleryCtrl.createGallery();
+ this.$httpBackend.flush();
+ this.$rootScope.$apply();
+ })
+ })
+})
\ No newline at end of file
diff --git a/lab-brae/test/edit-gallery-component-test.js b/lab-brae/test/edit-gallery-component-test.js
new file mode 100644
index 00000000..3e2857ed
--- /dev/null
+++ b/lab-brae/test/edit-gallery-component-test.js
@@ -0,0 +1,65 @@
+'use strict';
+
+describe('Edit Gallery Component', function() {
+ beforeEach( () => {
+ angular.mock.module('cfgram');
+ 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:8000/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();
+
+ expect(editGalleryCtrl.gallery._id).toEqual(mockBindings.gallery._id);
+ expect(editGalleryCtrl.gallery.name).toEqual(mockBindings.gallery.name);
+ expect(editGalleryCtrl.gallery.desc).toEqual(mockBindings.gallery.desc);
+
+ this.$httpBackend.flush();
+ this.$rootScope.$apply();
+ });
+ });
+});
\ No newline at end of file
diff --git a/lab-brae/test/gallery-item-component-test.js b/lab-brae/test/gallery-item-component-test.js
new file mode 100644
index 00000000..dd608687
--- /dev/null
+++ b/lab-brae/test/gallery-item-component-test.js
@@ -0,0 +1,64 @@
+'use strict';
+
+describe('Gallery Item Component', function() {
+ beforeEach(() => {
+ angular.mock.module('cfgram');
+ 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 = `${__API_URL__}/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);
+ }
+ };
+
+ this.$httpBackend.expectDELETE(url, headers).respond(204);
+
+ let galleryItemCtrl = this.$componentController('galleryItem', null, mockBindings);
+ galleryItemCtrl.deleteGallery();
+
+ this.$httpBackend.flush();
+ this.$rootScope.$apply();
+ });
+});
\ No newline at end of file
diff --git a/lab-brae/test/gallery-service-test.js b/lab-brae/test/gallery-service-test.js
new file mode 100644
index 00000000..d9113217
--- /dev/null
+++ b/lab-brae/test/gallery-service-test.js
@@ -0,0 +1,60 @@
+'use strict';
+
+describe('Gallery Service', function() {
+
+ beforeEach(() => {
+ angular.mock.module('cfgram');
+ 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(`${__API_URL__}/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 = 'testingID';
+ let headers = {
+ Authorization: 'Bearer test token',
+ Accept: 'application/json, text/plain, */*'
+ };
+
+ this.$httpBackend.expectDELETE(`${__API_URL__}/api/gallery/testingID`, headers)
+ .respond(204);
+
+ this.galleryService.deleteGallery(galleryID);
+ this.$httpBackend.flush();
+ this.$rootScope.$apply();
+ });
+ });
+});
\ No newline at end of file
diff --git a/lab-brae/webpack.config.js b/lab-brae/webpack.config.js
new file mode 100644
index 00000000..c8968b3d
--- /dev/null
+++ b/lab-brae/webpack.config.js
@@ -0,0 +1,46 @@
+'use strict';
+
+const dotenv = require('dotenv');
+const webpack = require('webpack');
+const HTMLPlugin = require('html-webpack-plugin');
+const ExtractTextPlugin = require('extract-text-webpack-plugin');
+
+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'])
+ }
+ ]
+ }
+};
\ No newline at end of file