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..f72ad6b3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,123 @@
+# Created by https://www.gitignore.io/api/node,vim,osx,macos,linux
+
+*node_modules
+
+###
+.env
+
+### Node ###
+# Logs
+logs
+*.log
+npm-debug.log*
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# ignore angular build directory
+build
+
+# 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
+
+
+
+### Vim ###
+# swap
+[._]*.s[a-v][a-z]
+[._]*.sw[a-p]
+[._]s[a-v][a-z]
+[._]sw[a-p]
+# session
+Session.vim
+# temporary
+.netrwhist
+*~
+# auto-generated tag files
+tags
+
+
+### OSX ###
+*.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+# Thumbnails
+._*
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+
+### macOS ###
+# Icon must end with two \r
+# Thumbnails
+# Files that might appear in the root of a volume
+# Directories potentially created on remote AFP share
+
+
+### 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*
+
+# End of https://www.gitignore.io/api/node,vim,osx,macos,linux
diff --git a/app/component/gallery/create-gallery/_create-gallery.scss b/app/component/gallery/create-gallery/_create-gallery.scss
new file mode 100644
index 00000000..d669ecab
--- /dev/null
+++ b/app/component/gallery/create-gallery/_create-gallery.scss
@@ -0,0 +1,10 @@
+@import "../../../scss/lib/theme/vars";
+
+.create-gallery {
+ button {
+ left: 250px;
+ padding-left: 25px;
+ padding-right: 25px;
+ border-radius: 5px;
+ }
+}
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..c1adb902
--- /dev/null
+++ b/app/component/gallery/create-gallery/create-gallery.html
@@ -0,0 +1,27 @@
+
+ Create a new gallery.
+
+
+
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..0598a617
--- /dev/null
+++ b/app/component/gallery/edit-gallery/edit-gallery.html
@@ -0,0 +1,21 @@
+
diff --git a/app/component/gallery/edit-gallery/edit-gallery.js b/app/component/gallery/edit-gallery/edit-gallery.js
new file mode 100644
index 00000000..2564a90e
--- /dev/null
+++ b/app/component/gallery/edit-gallery/edit-gallery.js
@@ -0,0 +1,21 @@
+'use strict';
+
+require('./_edit-gallery.scss');
+
+module.exports = {
+ template: require('./edit-gallery.html'),
+ controller: ['$log', 'galleryService', EditGalleryController],
+ controllerAs: 'editGalleryCtrl',
+ bindings: {
+ gallery: '<'
+ }
+};
+
+function EditGalleryController($log, galleryService) {
+ $log.debug('EditGalleryController');
+
+ this.updateGallery = function() {
+ $log.debug('editGalleryCtrl.updateGallery');
+ galleryService.updateGallery(this.gallery._id, this.gallery);
+ };
+};
diff --git a/app/component/gallery/gallery-item/_gallery-item.scss b/app/component/gallery/gallery-item/_gallery-item.scss
new file mode 100644
index 00000000..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..821fa2c1
--- /dev/null
+++ b/app/component/gallery/gallery-item/gallery-item.html
@@ -0,0 +1,21 @@
+
+
+
+ name:
+ {{ 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..18453222
--- /dev/null
+++ b/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);
+ };
+};
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..54eb0559
--- /dev/null
+++ b/app/component/gallery/thumbnail-container/thumbnail-container.html
@@ -0,0 +1,8 @@
+
+
{{ 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..3554de0f
--- /dev/null
+++ b/app/component/gallery/thumbnail/thumbnail.html
@@ -0,0 +1,4 @@
+
+
![{{ thumbnailCtrl.pic.desc }}]()
+
+
diff --git a/app/component/gallery/thumbnail/thumbnail.js b/app/component/gallery/thumbnail/thumbnail.js
new file mode 100644
index 00000000..5ff9ee7f
--- /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.deletePic(this.gallery,this.pic._id);
+ };
+}
diff --git a/app/component/gallery/upload-pic/_upload-pic.scss b/app/component/gallery/upload-pic/_upload-pic.scss
new file mode 100644
index 00000000..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..b32ef682
--- /dev/null
+++ b/app/component/gallery/upload-pic/upload-pic.html
@@ -0,0 +1,23 @@
+
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..da7a4d56
--- /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..97b0b545
--- /dev/null
+++ b/app/component/landing/login/login.html
@@ -0,0 +1,32 @@
+
+
diff --git a/app/component/landing/login/login.js b/app/component/landing/login/login.js
new file mode 100644
index 00000000..dec7c927
--- /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..44bc8764
--- /dev/null
+++ b/app/component/landing/signup/signup.html
@@ -0,0 +1,25 @@
+
diff --git a/app/component/landing/signup/signup.js b/app/component/landing/signup/signup.js
new file mode 100644
index 00000000..b5923714
--- /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('signupCtrl.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..3298f347
--- /dev/null
+++ b/app/component/navbar/_navbar.scss
@@ -0,0 +1,26 @@
+@import '../../scss/lib/theme/_vars.scss';
+
+.btn-std {
+ display: inline-block;
+ border: 0.5px solid black;
+ border-radius: 6px;
+ font-size: 1vw;
+ padding-left: 5px;
+ padding-right: 5px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ margin-top: 10px;
+ margin-right: 10px;
+}
+
+.navbar {
+ width: 100%;
+ ul {
+ display: inline-block;
+ }
+
+ nav, div {
+ display: inline-block;
+ float: right;
+ }
+}
diff --git a/app/component/navbar/navbar.html b/app/component/navbar/navbar.html
new file mode 100644
index 00000000..ea3fccf8
--- /dev/null
+++ b/app/component/navbar/navbar.html
@@ -0,0 +1,10 @@
+
diff --git a/app/component/navbar/navbar.js b/app/component/navbar/navbar.js
new file mode 100644
index 00000000..7e68d70a
--- /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/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/entry.js b/app/entry.js
new file mode 100644
index 00000000..f8762a51
--- /dev/null
+++ b/app/entry.js
@@ -0,0 +1,40 @@
+'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);
+});
diff --git a/app/index.html b/app/index.html
new file mode 100644
index 00000000..f4d84d6d
--- /dev/null
+++ b/app/index.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+ cfgram.
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/scss/lib/base/_base.scss b/app/scss/lib/base/_base.scss
new file mode 100644
index 00000000..35fe4102
--- /dev/null
+++ b/app/scss/lib/base/_base.scss
@@ -0,0 +1,3 @@
+body {
+ background: orange;
+}
diff --git a/app/scss/lib/base/_reset.scss b/app/scss/lib/base/_reset.scss
new file mode 100644
index 00000000..0e5f2e0c
--- /dev/null
+++ b/app/scss/lib/base/_reset.scss
@@ -0,0 +1,43 @@
+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/_layout.scss b/app/scss/lib/layout/_layout.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/app/scss/lib/theme/_vars.scss b/app/scss/lib/theme/_vars.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/app/scss/main.scss b/app/scss/main.scss
new file mode 100644
index 00000000..0d698e36
--- /dev/null
+++ b/app/scss/main.scss
@@ -0,0 +1,3 @@
+@import './lib/base/_reset.scss';
+@import './lib/base/_base.scss';
+@import './lib/layout/_layout.scss';
diff --git a/app/service/auth-service.js b/app/service/auth-service.js
new file mode 100644
index 00000000..41fe4c15
--- /dev/null
+++ b/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/app/service/gallery-service.js b/app/service/gallery-service.js
new file mode 100644
index 00000000..9969ad01
--- /dev/null
+++ b/app/service/gallery-service.js
@@ -0,0 +1,141 @@
+'use strict';
+
+module.exports = ['$q', '$log', '$http', 'authService', galleryService];
+
+function galleryService($q, $log, $http, authService) {
+ $log.debug('galleryService()');
+
+ let service = {};
+ service.galleries = [];
+
+ service.createGallery = function(gallery) {
+ $log.debug('galleryService.createGallery()');
+
+ return authService.getToken()
+ .then( token => {
+ let url = `${__API_URL__}/api/gallery`;
+ 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, galleryData) {
+ return authService.getToken()
+ .then( token => {
+ let url = `${__API_URL__}/api/gallery/${galleryID}`;
+ let config = {
+ headers: {
+ Accept: 'application/json',
+ Authorization: `Bearer ${token}`
+ }
+ };
+ });
+ };
+
+ 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.updateGallery()');
+
+ 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;
+};
diff --git a/app/service/pic-service.js b/app/service/pic-service.js
new file mode 100644
index 00000000..9968e9e7
--- /dev/null
+++ b/app/service/pic-service.js
@@ -0,0 +1,72 @@
+'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.deletePic = function(galleryData, clickedPic) {
+ $log.debug('service.deletePic');
+
+ return authService.getToken()
+ .then( token => {
+ let url = `${__API_URL__}/api/gallery/${galleryData._id}/pic/${clickedPic}`;
+ let config = {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ };
+
+ return $http.delete(url, config);
+ })
+ .then( () => {
+ for (let i=0; i < galleryData.pics.length; i++) {
+ if (galleryData.pics[i]._id === clickedPic) {
+ 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..260edd21
--- /dev/null
+++ b/app/view/home/_home.scss
@@ -0,0 +1,27 @@
+@import '../../scss/lib/theme/_vars.scss';
+
+.container {
+ margin: auto;
+ width: 99%;
+
+ ul {
+ margin-top: 20px;
+ margin-left: 10px;
+ margin-bottom: 20px;
+ padding-bottom: 200px;
+ }
+ li {
+ display: block;
+ border-radius: 5px;
+ font-size: 2vw;
+ margin-bottom: 10px;
+ width: 99%;
+ padding-top: 10px;
+ span {
+ text-align: left;
+ padding-left: 10px;
+ padding-bottom: 10px;
+ display: block;
+ }
+ }
+}
diff --git a/app/view/home/home-controller.js b/app/view/home/home-controller.js
new file mode 100644
index 00000000..e51b99a1
--- /dev/null
+++ b/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/app/view/home/home.html b/app/view/home/home.html
new file mode 100644
index 00000000..5dd0612b
--- /dev/null
+++ b/app/view/home/home.html
@@ -0,0 +1,14 @@
+
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..ad4e3b7e
--- /dev/null
+++ b/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';
+}
diff --git a/app/view/landing/landing.html b/app/view/landing/landing.html
new file mode 100644
index 00000000..efa4547a
--- /dev/null
+++ b/app/view/landing/landing.html
@@ -0,0 +1,25 @@
+
diff --git a/karma.config.js b/karma.config.js
new file mode 100644
index 00000000..3ae9469c
--- /dev/null
+++ b/karma.config.js
@@ -0,0 +1,76 @@
+// Karma configuration
+// Generated on Wed Mar 22 2017 12:51:00 GMT-0700 (PDT)
+
+const webpackConfig = require('./webpack.config.js');
+webpackConfig.entry = {};
+
+module.exports = function(config) {
+ config.set({
+
+ // to wire to webpack
+ webpack: webpackConfig,
+
+ // 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: [
+ 'test/**/*-test.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']
+ },
+
+
+ // 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: ['PhantomJS'],
+
+
+ // Continuous Integration mode
+ // if true, Karma captures browsers, runs the tests and exits
+ singleRun: false,
+
+ // Concurrency level
+ // how many browser should be started simultaneous
+ concurrency: Infinity
+ });
+};
diff --git a/package.json b/package.json
new file mode 100644
index 00000000..50f5d281
--- /dev/null
+++ b/package.json
@@ -0,0 +1,47 @@
+{
+ "name": "25-angular_auth",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "build": "./node_modules/webpack/bin/webpack.js",
+ "watch": "./node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot",
+ "lint": "./node_modules/eslint/bin/eslint.js **/*.js",
+ "test": "./node_modules/karma/bin/karma start --single-run",
+ "test-watch": "./node_modules/karma/bin/karma start"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "angular": "^1.6.4",
+ "angular-animate": "^1.6.4",
+ "angular-route": "^1.6.4",
+ "angular-touch": "^1.6.4",
+ "angular-ui-router": "^0.4.2",
+ "babel-core": "^6.24.1",
+ "babel-loader": "^7.0.0",
+ "babel-preset-es2015": "^6.24.1",
+ "camelcase": "^4.1.0",
+ "clean-webpack-plugin": "^0.1.16",
+ "css-loader": "^0.28.0",
+ "dotenv": "^4.0.0",
+ "eslint": "^3.19.0",
+ "extract-text-webpack-plugin": "^2.1.0",
+ "file-loader": "^0.11.1",
+ "html-loader": "^0.4.5",
+ "html-webpack-plugin": "^2.28.0",
+ "ng-file-upload": "^12.2.13",
+ "node-sass": "^4.5.2",
+ "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.4.1"
+ },
+ "devDependencies": {
+ "webpack-dev-server": "^2.4.5"
+ }
+}
diff --git a/webpack.config.js b/webpack.config.js
new file mode 100644
index 00000000..b13d76bc
--- /dev/null
+++ b/webpack.config.js
@@ -0,0 +1,43 @@
+'use strict';
+
+require('dotenv').load();
+
+const webpack = require('webpack');
+const HTMLPlugin = require('html-webpack-plugin');
+const ExtractTextPlugin = require('extract-text-webpack-plugin');
+
+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)})
+ ],
+ module: {
+ rules: [
+ {
+ test: /\.js$/,
+ exclude: /node_modules/,
+ loader: 'babel-loader'
+ },
+ {
+ test: /\.html$/,
+ loader: 'html-loader'
+ },
+ {
+ test: /\.png$/,
+ loader: 'file-loader',
+ options: { name: '[path][name].[hash][ext]' }
+ },
+ {
+ test: /\.scss$/,
+ loader: ExtractTextPlugin.extract(['css-loader', 'sass-loader'])
+ }
+ ]
+ }
+};