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..8dc68078
--- /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..603220a4
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,116 @@
+# Created by https://www.gitignore.io/api/node,macos,windows
+
+### 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
+
+### 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
+
+
+### 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
+
+# Angular Build Directory
+build/
+
+# End of https://www.gitignore.io/api/node,macos,windows
diff --git a/wireframes/assets/cf-logo.png b/app/assets/images/cf-logo.png
similarity index 100%
rename from wireframes/assets/cf-logo.png
rename to app/assets/images/cf-logo.png
diff --git a/app/assets/images/cf.png b/app/assets/images/cf.png
new file mode 100644
index 00000000..2aecf242
Binary files /dev/null and b/app/assets/images/cf.png differ
diff --git a/app/assets/images/spritesheet.png b/app/assets/images/spritesheet.png
new file mode 100644
index 00000000..73b98031
Binary files /dev/null and b/app/assets/images/spritesheet.png differ
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..0fe57ca9
--- /dev/null
+++ b/app/component/gallery/create-gallery/_create-gallery.scss
@@ -0,0 +1,9 @@
+@import '../../../scss/lib/theme/vars';
+
+label {
+ color: $dark * 2;
+ float: left;
+ margin-left: 40px;
+ padding-top: 20px;
+ font-size: 20px;
+}
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..e31e1b20
--- /dev/null
+++ b/app/component/gallery/create-gallery/create-gallery.html
@@ -0,0 +1,31 @@
+
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..cabb2be6
--- /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..e69de29b
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..f29c12d7
--- /dev/null
+++ b/app/component/gallery/edit-gallery/edit-gallery.html
@@ -0,0 +1,25 @@
+
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..17fc2434
--- /dev/null
+++ b/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);
+ };
+}
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..e69de29b
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..1ee24660
--- /dev/null
+++ b/app/component/gallery/gallery-item/gallery-item.html
@@ -0,0 +1,22 @@
+
+
+
+ name:
+ {{ galleryItemCtrl.gallery.name | hashtagify: galleryItemCtrl.gallery.name }}
+
+
+
+ description:
+ {{ galleryItemCtrl.gallery.desc }}
+
+
+
+
+
+
+ edit
+ delete
+
+
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..1d498013
--- /dev/null
+++ b/app/component/gallery/gallery-item/gallery-item.js
@@ -0,0 +1,25 @@
+'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)
+ .then( () => {
+ this.deleteDone(this.gallery);
+ });
+ };
+}
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..e69de29b
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..97e5ccf3
--- /dev/null
+++ b/app/component/gallery/thumbnail-container/thumbnail-container.html
@@ -0,0 +1,10 @@
+
+
{{ 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..e69de29b
diff --git a/app/component/gallery/thumbnail/thumbnail.html b/app/component/gallery/thumbnail/thumbnail.html
new file mode 100644
index 00000000..27a44836
--- /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..10b09cb8
--- /dev/null
+++ b/app/component/gallery/thumbnail/thumbnail.js
@@ -0,0 +1,23 @@
+'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);
+ };
+}
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..e69de29b
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..ebd90532
--- /dev/null
+++ b/app/component/gallery/upload-pic/upload-pic.html
@@ -0,0 +1,24 @@
+
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..6a950f16
--- /dev/null
+++ b/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;
+ });
+ };
+}
diff --git a/app/component/landing/login/_login.scss b/app/component/landing/login/_login.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/app/component/landing/login/login.html b/app/component/landing/login/login.html
new file mode 100644
index 00000000..41f502fa
--- /dev/null
+++ b/app/component/landing/login/login.html
@@ -0,0 +1,37 @@
+
diff --git a/app/component/landing/login/login.js b/app/component/landing/login/login.js
new file mode 100644
index 00000000..77c2c83e
--- /dev/null
+++ b/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');
+ });
+ };
+}
diff --git a/app/component/landing/signup/_signup.scss b/app/component/landing/signup/_signup.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/app/component/landing/signup/signup.html b/app/component/landing/signup/signup.html
new file mode 100644
index 00000000..07152859
--- /dev/null
+++ b/app/component/landing/signup/signup.html
@@ -0,0 +1,27 @@
+
diff --git a/app/component/landing/signup/signup.js b/app/component/landing/signup/signup.js
new file mode 100644
index 00000000..3da31c61
--- /dev/null
+++ b/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('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..5d926d14
--- /dev/null
+++ b/app/component/navbar/_navbar.scss
@@ -0,0 +1,14 @@
+@import '../../scss/lib/theme/vars';
+
+nav {
+ float: right;
+ margin-right: 25px;
+
+ .btn-std {
+ margin-top: 10px;
+ width: 125px;
+ height: 30px;
+ background-color: $white;
+ color: $dark;
+ }
+}
diff --git a/app/component/navbar/navbar.html b/app/component/navbar/navbar.html
new file mode 100644
index 00000000..f36816b3
--- /dev/null
+++ b/app/component/navbar/navbar.html
@@ -0,0 +1,10 @@
+
+
+ cf gram
+
+
+
diff --git a/app/component/navbar/navbar.js b/app/component/navbar/navbar.js
new file mode 100644
index 00000000..79cb21a8
--- /dev/null
+++ b/app/component/navbar/navbar.js
@@ -0,0 +1,44 @@
+'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..5d187b8b
--- /dev/null
+++ b/app/config/log-config.js
@@ -0,0 +1,7 @@
+'use strict';
+
+module.exports = ['$logProvider', logConfig];
+
+function logConfig($logProvider) {
+ $logProvider.debugEnabled(true);//eslint-disable-line
+}
diff --git a/app/config/router-config.js b/app/config/router-config.js
new file mode 100644
index 00000000..31d817b6
--- /dev/null
+++ b/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/app/directive/_social-icons.scss b/app/directive/_social-icons.scss
new file mode 100644
index 00000000..dae334cd
--- /dev/null
+++ b/app/directive/_social-icons.scss
@@ -0,0 +1,50 @@
+.media-icons {
+ float: left;
+ margin-left: 3vw;
+ position: relative;
+ bottom: 4vw;
+}
+
+.sprite {
+ background-image: url('../assets/images/spritesheet.png');
+ background-repeat: no-repeat;
+ display: block;
+ float: right;
+ margin-bottom: 3vw;
+}
+
+.sprite-github {
+ width: 30px;
+ height: 30px;
+ background-position: -5px -5px;
+}
+
+.sprite-gmail {
+ width: 30px;
+ height: 30px;
+ background-position: -45px -5px;
+}
+
+.sprite-linkedin {
+ width: 30px;
+ height: 30px;
+ background-position: -5px -45px;
+}
+
+.sprite-stackoverflow {
+ width: 30px;
+ height: 30px;
+ background-position: -45px -45px;
+}
+
+.sprite-tumblr {
+ width: 30px;
+ height: 30px;
+ background-position: -85px -5px;
+}
+
+.sprite-twitter {
+ width: 30px;
+ height: 30px;
+ background-position: -85px -45px;
+}
diff --git a/app/directive/social-icons.html b/app/directive/social-icons.html
new file mode 100644
index 00000000..6e7cd565
--- /dev/null
+++ b/app/directive/social-icons.html
@@ -0,0 +1,7 @@
+
diff --git a/app/directive/social-icons.js b/app/directive/social-icons.js
new file mode 100644
index 00000000..30adc9f0
--- /dev/null
+++ b/app/directive/social-icons.js
@@ -0,0 +1,22 @@
+'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($log) {
+ $log.debug('SocialIconsController');
+
+ this.icons = ['github', 'gmail', 'linkedin', 'stackoverflow', 'tumblr', 'twitter'];
+}
diff --git a/app/entry.js b/app/entry.js
new file mode 100644
index 00000000..a8c2c29e
--- /dev/null
+++ b/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);
+});
diff --git a/app/filter/gallery-search.js b/app/filter/gallery-search.js
new file mode 100644
index 00000000..32609edd
--- /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/filter/hashtagify.js b/app/filter/hashtagify.js
new file mode 100644
index 00000000..fd8c44bd
--- /dev/null
+++ b/app/filter/hashtagify.js
@@ -0,0 +1,15 @@
+'use strict';
+
+module.exports = function() {
+ return function(input) {
+ if (!input) return 'coolhashtagbruh';
+
+ let stringInput = input;
+ if (stringInput !== typeof 'string') stringInput = input.toString();
+
+ let hashtag = stringInput.match(/[\w]/g);
+ if (hashtag) return `#${hashtag.join('')}`;
+
+ return 'invalid hashtag';
+ };
+};
diff --git a/app/index.html b/app/index.html
new file mode 100644
index 00000000..9be0ab02
--- /dev/null
+++ b/app/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+ cf gramz
+
+
+
+
+
+
+
+
+
+
diff --git a/app/scss/lib/base/_base.scss b/app/scss/lib/base/_base.scss
new file mode 100644
index 00000000..086d42c0
--- /dev/null
+++ b/app/scss/lib/base/_base.scss
@@ -0,0 +1,56 @@
+@import '../theme/vars';
+
+body {
+ font-family: helvetica;
+ background-color: $light;
+}
+
+header {
+ background-color: $dark;
+
+ h2 {
+ color: $white;
+ }
+
+ .icon {
+ background-image: url('../assets/images/cf-logo.png');
+ background-size: cover;
+ }
+}
+
+p {
+ float: right;
+ margin-top: 10px;
+ margin-right: 150px;
+}
+
+a {
+ text-decoration: none;
+ color: black;
+ font-weight: bolder;
+}
+
+.btn-std {
+ background-color: $dark;
+ color: $white;
+ font-size: 15px;
+ border-radius: 5px;
+
+ &:hover {
+ cursor: pointer;
+ background-color: $dark / 2;
+ }
+}
+
+footer {
+ background-color: $dark / 2;
+}
+
+.input-std {
+ border-radius: 3px;
+}
+
+::-webkit-input-placeholder {
+ text-indent: 10px;
+ font-size: 12px;
+}
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..67309518
--- /dev/null
+++ b/app/scss/lib/layout/_content.scss
@@ -0,0 +1,35 @@
+.sign {
+ float: left;
+ padding-top: 30px;
+ margin-left: 4%;
+}
+
+.clearfix {
+ clear: both;
+}
+
+.input-std {
+ width: 90%;
+ height: 30px;
+ margin-left: 4%;
+ margin-top: 20px;
+}
+
+.btn-std {
+ float: right;
+ height: 35px;
+ width: 150px;
+ margin-top: 20px;
+ margin-right: 5%;
+}
+
+a{
+ float: right;
+ margin-right: 5%;
+ margin-top: 10px;
+}
+
+p {
+ margin-top: 10px;
+ margin-right: 10px;
+}
diff --git a/app/scss/lib/layout/_footer.scss b/app/scss/lib/layout/_footer.scss
new file mode 100644
index 00000000..56fa1502
--- /dev/null
+++ b/app/scss/lib/layout/_footer.scss
@@ -0,0 +1,6 @@
+footer {
+ width: 100vw;
+ height: 10vw;
+ position: relative;
+ margin-top: 3vw;
+}
diff --git a/app/scss/lib/layout/_header.scss b/app/scss/lib/layout/_header.scss
new file mode 100644
index 00000000..0172ef2e
--- /dev/null
+++ b/app/scss/lib/layout/_header.scss
@@ -0,0 +1,23 @@
+header {
+ width: 100%;
+ height: 80px;
+ overflow: auto;
+
+ div {
+ float: left;
+ height: 50px;
+ width: 50px;
+ margin-top: 15px;
+ margin-left: 25px;
+ }
+
+ h2 {
+ float: left;
+ margin-left: 10px;
+ margin-top: 30px;
+ font-size: 20px;
+ line-height: 20px;
+ font-weight: 100;
+ letter-spacing: 1.5px;
+ }
+}
diff --git a/app/scss/lib/theme/_vars.scss b/app/scss/lib/theme/_vars.scss
new file mode 100644
index 00000000..b4709a0c
--- /dev/null
+++ b/app/scss/lib/theme/_vars.scss
@@ -0,0 +1,3 @@
+$dark: #333;
+$white: #fff;
+$light: #b3b3b3;
diff --git a/app/scss/main.scss b/app/scss/main.scss
new file mode 100644
index 00000000..fdeab693
--- /dev/null
+++ b/app/scss/main.scss
@@ -0,0 +1,12 @@
+//+++++ BASE +++++\\
+@import './lib/base/reset';
+@import './lib/base/base';
+
+//+++++ LAYOUTZ +++++\\
+@import './lib/layout/header';
+@import './lib/layout/content';
+@import './lib/layout/footer';
+
+//+++++ VIEWS +++++\\
+@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..9c711054
--- /dev/null
+++ b/app/service/auth-service.js
@@ -0,0 +1,89 @@
+'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..7356effa
--- /dev/null
+++ b/app/service/gallery-service.js
@@ -0,0 +1,158 @@
+'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);
+ return $q.reject(err);
+ });
+ };
+
+ 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('got your galleries');
+ let galleries = res.data;
+ service.galleries = galleries;
+ return service.galleries;
+ })
+ .catch( err => {
+ $log.error(err.message);
+ return $q.reject(err);
+ });
+ };
+
+ service.deleteGalleries = function(galleryID, galleryData) {
+ $log.debug('galleryService.deleteGalleries');
+ $log.log(galleryData);
+ return authService.getToken()
+ .then( token => {
+ let url = `${__API_URL__}/api/gallery/${galleryID}`; //eslint-disable-line
+ let config = {
+ headers: {
+ Accept: 'application/json',
+ Authorization: `Bearer ${token}`
+ }
+ };
+
+ return $http.delete(url, config);
+ })
+ .then( res => {
+ $log.log('gallery removed');
+ return JSON.parse(res.status);
+ })
+ .catch( err => {
+ $log.error(err);
+ return $q.reject(err);
+ });
+ };
+
+ service.updateGallery = function(galleryID, galleryData) {
+ $log.debug('galleryService.updateGalleries');
+
+ 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 => {
+ for (let i = 0; i < service.galleries.length; i++) {
+ let current = service.galleries[i];
+ if (current._id === galleryID) {
+ service.galleries[i] = res.data;
+ break;
+ }
+ }
+
+ $log.log('update res data', res.data);
+ 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}`,
+ Accept: 'application/json'
+ }
+ };
+
+ return $http.delete(url, config);
+ })
+ .then( res => {
+ $log.log(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;
+ }
+ }
+ return res;
+ })
+ .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..57843d66
--- /dev/null
+++ b/app/service/pic-service.js
@@ -0,0 +1,75 @@
+'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('picService.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 => {
+ $log.debug('galleryData', galleryData);
+ galleryData.pics.unshift(res.data);
+ $log.debug('pics array', galleryData.pics);
+ 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}`; //eslint-disable-line
+ 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;
+}
diff --git a/app/view/home/_home.scss b/app/view/home/_home.scss
new file mode 100644
index 00000000..0149867b
--- /dev/null
+++ b/app/view/home/_home.scss
@@ -0,0 +1,31 @@
+@import '../../scss/lib/theme/vars';
+
+h2 {
+ margin-left: 5%;
+ margin-top: 50px;
+ color: $dark * 2;
+ font-size: 1.3em;
+}
+
+.gallery-box {
+ height: 80px;
+ width: 90%;
+ margin-left: 5%;
+ margin-top: 20px;
+ background-color: $light / 1.5;
+ border-radius: 5px;
+ padding: 5px;
+
+ li {
+ margin-left: 10px;
+ font-size: 15px;
+ line-height: 25px;
+ color: $dark / 2;
+ font-weight: bolder;
+
+ span {
+ color: $dark;
+ font-weight: normal;
+ }
+ }
+}
diff --git a/app/view/home/home-controller.js b/app/view/home/home-controller.js
new file mode 100644
index 00000000..c33fe804
--- /dev/null
+++ b/app/view/home/home-controller.js
@@ -0,0 +1,30 @@
+'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.fetchGalleries();
+
+ this.galleryDeleteDone = function(gallery) {
+ if (this.currentGallery._id === gallery._id) {
+ this.currentGallery = null;
+ }
+ };
+
+ $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..1f76324c
--- /dev/null
+++ b/app/view/home/home.html
@@ -0,0 +1,27 @@
+
diff --git a/app/view/landing/_landing.scss b/app/view/landing/_landing.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/app/view/landing/landing-controller.js b/app/view/landing/landing-controller.js
new file mode 100644
index 00000000..f0436dfe
--- /dev/null
+++ b/app/view/landing/landing-controller.js
@@ -0,0 +1,11 @@
+'use strict';
+
+require('./_landing.scss');
+
+module.exports = ['$log', '$location', '$rootScope', LandingController];
+
+function LandingController($log, $location) {
+ $log.debug('LandingController');
+
+ let url = $location.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..fe53090e
--- /dev/null
+++ b/app/view/landing/landing.html
@@ -0,0 +1,27 @@
+
diff --git a/karma.conf.js b/karma.conf.js
new file mode 100644
index 00000000..917dc042
--- /dev/null
+++ b/karma.conf.js
@@ -0,0 +1,29 @@
+const webpack = require('./webpack.config.js');
+delete webpack.entry;
+
+module.exports = function(config) {
+ config.set({
+ webpack,
+ basePath: '',
+ frameworks: ['jasmine'],
+ files: [
+ 'app/entry.js',
+ 'test/**/*-test.js',
+ 'node_modules/angular-mocks/angular-mocks.js'
+ ],
+ exclude: [
+ ],
+ preprocessors: {
+ 'test/**/*-test.js': ['webpack'],
+ 'app/entry.js': ['webpack']
+ },
+ reporters: ['mocha'],
+ port: 9876,
+ colors: true,
+ logLevel: config.LOG_INFO,
+ autoWatch: true,
+ browsers: ['Chrome'],
+ singleRun: false,
+ concurrency: Infinity
+ });
+};
diff --git a/package.json b/package.json
new file mode 100644
index 00000000..9416d5e5
--- /dev/null
+++ b/package.json
@@ -0,0 +1,62 @@
+{
+ "name": "lab-dana",
+ "version": "1.0.0",
+ "description": " Lab 25 - Client Side Auth ======",
+ "main": "karma.config.js",
+ "scripts": {
+ "build": "./node_modules/webpack/bin/webpack.js",
+ "build-watch": "./node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot",
+ "lint": "eslint **/*.js --ignore-pattern node_modules/",
+ "test": "./node_modules/karma/bin/karma start --single-run",
+ "test-watch": "./node_modules/karma/bin/karma start"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/dkulp23/25-angular_auth.git"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "bugs": {
+ "url": "https://github.com/dkulp23/25-angular_auth/issues"
+ },
+ "homepage": "https://github.com/dkulp23/25-angular_auth#readme",
+ "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",
+ "eslint": "^3.18.0",
+ "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..ecfef731
--- /dev/null
+++ b/test/auth-service-test.js
@@ -0,0 +1,29 @@
+'use strict';
+
+describe('Auth Service', function() {
+ beforeEach(() => {
+ angular.mock.module('cfgram'); //eslint-disable-line
+ angular.mock.inject(($rootScope, authService, $window) => { //eslint-disable-line
+ this.$window = $window;
+ this.$rootScope = $rootScope;
+ this.authService = authService;
+ });
+ });
+
+ 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..730d5641
--- /dev/null
+++ b/test/edit-gallery-component-test.js
@@ -0,0 +1,63 @@
+'use strict';
+
+describe('Edit Gallery Component', function() {
+
+ beforeEach( () => {
+ angular.mock.module('cfgram'); //eslint-disable-line
+ angular.mock.inject(($rootScope, $componentController, $httpBackend, $log, authService) => { //eslint-disable-line
+ this.$rootScope = $rootScope;
+ this.$componentController = $componentController;
+ this.$httpBackend = $httpBackend;
+ this.authService = authService;
+ this.$log = $log;
+ });
+ });
+
+ 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';//eslint-disable-line
+ let headers = {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ Authorization: 'Bearer test token'
+ };
+
+ this.$httpBackend.expectPUT(url, {
+ _id: '12345',
+ name: 'updated name',
+ desc: 'updated desc'
+ }, headers)
+ .respond(200);
+
+ let mockBindings = {
+ gallery: {
+ _id: '12345',
+ name: 'updated name',
+ desc: 'updated desc'
+ }
+ };
+
+ let editGalleryCtrl = this.$componentController('editGallery', null, mockBindings);
+ editGalleryCtrl.updateGallery(editGalleryCtrl.gallery._id, editGalleryCtrl.gallery);
+ expect(editGalleryCtrl.gallery.name).toEqual('updated name');
+ expect(editGalleryCtrl.gallery.desc).toEqual('updated desc');
+ this.$httpBackend.flush();
+ this.$rootScope.$apply();
+ });
+ });
+});
diff --git a/test/example-test.js b/test/example-test.js
new file mode 100644
index 00000000..23ae19f5
--- /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..c68a385f
--- /dev/null
+++ b/test/gallery-item-component-test.js
@@ -0,0 +1,67 @@
+'use strict';
+
+describe('Gallery Item Component', function() {
+
+ beforeEach(() => {
+ angular.mock.module('cfgram'); //eslint-disable-line
+ angular.mock.inject(($rootScope, $componentController, $httpBackend, authService, galleryService) => { //eslint-disable-line
+ this.$rootScope = $rootScope;
+ this.$componentController = $componentController;
+ this.$httpBackend = $httpBackend;
+ this.authService = authService;
+ this.galleryService = galleryService;
+ });
+ });
+
+ 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();
+ });
+ });
+
+ describe('galleryItemCtrl.deleteGallery', () => {
+ it('should delete a gallery', () => {
+ let mockBindings = {
+ gallery: {
+ _id: '12345',
+ name: 'test name',
+ desc: 'test description',
+ pics: []
+ },
+ deleteDone: function(data) {
+ expect(data._id).toEqual(mockBindings.gallery._id);
+ expect(data.name).toEqual(mockBindings.gallery.name);
+ }
+ };
+
+ let headers = {
+ Authorization: 'Bearer test token',
+ Accept: 'application/json'
+ };
+
+ this.$httpBackend.expectDELETE('http://localhost:3000/api/gallery/12345', 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..ae6422de
--- /dev/null
+++ b/test/gallery-service-test.js
@@ -0,0 +1,76 @@
+'use strict';
+
+const url = `${__API_URL__}/api/gallery`; //eslint-disable-line
+
+describe('Gallery Service', function() {
+
+ beforeEach(() => {
+ angular.mock.module('cfgram'); //eslint-disable-line
+ angular.mock.inject(($rootScope, authService, galleryService, $window, $httpBackend, $log) => { //eslint-disable-line
+ this.$window = $window;
+ this.$rootScope = $rootScope;
+ this.authService = authService;
+ this.galleryService = galleryService;
+ this.$httpBackend = $httpBackend;
+ this.$log = $log;
+ });
+ });
+
+ 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(url, galleryData, headers)
+ .respond(200, {
+ _id: '1234',
+ username: 'testuser',
+ name: galleryData.name,
+ desc: galleryData.desc,
+ pics: []
+ });
+
+ this.galleryService.createGallery(galleryData)
+ .then( gallery => {
+ expect(gallery._id).toEqual('1234');
+ expect(gallery.name).toEqual(galleryData.name);
+ });
+ this.$httpBackend.flush();
+ this.$rootScope.$apply();
+ });
+ });
+
+ describe('galleryService.deleteGallery', () => {
+ it('should delete a gallery', () => {
+ let galleryData = {
+ _id: '1234',
+ name: 'example gallery',
+ desc: 'example description'
+ };
+
+ let headers = {
+ Authorization: 'Bearer test token',
+ Accept: 'application/json'
+ };
+
+ this.$httpBackend.expectDELETE(`${url}/${galleryData._id}`, headers)
+ .respond(204, {});
+
+ this.galleryService.deleteGallery(galleryData._id)
+ .then( res => {
+ expect(res.status).toEqual(204);
+ });
+ this.$log.debug('this', this);
+ this.$httpBackend.flush();
+ this.$rootScope.$apply();
+ });
+ });
+});
diff --git a/webpack.config.js b/webpack.config.js
new file mode 100644
index 00000000..e6228a18
--- /dev/null
+++ b/webpack.config.js
@@ -0,0 +1,50 @@
+'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: {
+ path: `${__dirname}/build`,
+ filename: 'bundle.js'
+ },
+ 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'])
+ },
+ {
+ test: /\.png$/,
+ loader: 'url-loader'
+ }
+ ]
+ }
+};