diff --git a/lab-claudia/.babelrc b/lab-claudia/.babelrc new file mode 100644 index 0000000..c13c5f6 --- /dev/null +++ b/lab-claudia/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["es2015"] +} diff --git a/lab-claudia/.client.env b/lab-claudia/.client.env new file mode 100644 index 0000000..d6fb96d --- /dev/null +++ b/lab-claudia/.client.env @@ -0,0 +1,4 @@ +NODE_ENV="dev" +TITLE="Photo Gallery" +API_URL="http://localhost:3000" +GOOGLE_CLIENT_ID="308322069962-972jlkfpbov9cd88ot0dtdtc2lgfutdl.apps.googleusercontent.com" diff --git a/lab-claudia/.coveralls.yml b/lab-claudia/.coveralls.yml new file mode 100644 index 0000000..7e77bc7 --- /dev/null +++ b/lab-claudia/.coveralls.yml @@ -0,0 +1,2 @@ +service_name: travis-ci +repo_token: yyiFHQhz99RneXclnlk7FeYimmLptZvAK diff --git a/lab-claudia/.eslintignore b/lab-claudia/.eslintignore new file mode 100644 index 0000000..05b1cf3 --- /dev/null +++ b/lab-claudia/.eslintignore @@ -0,0 +1,5 @@ +**/node_modules/* +**/vendor/* +**/*.min.js +**/coverage/* +**/build/* diff --git a/lab-claudia/.eslintrc b/lab-claudia/.eslintrc new file mode 100644 index 0000000..564f18c --- /dev/null +++ b/lab-claudia/.eslintrc @@ -0,0 +1,28 @@ +{ + "rules": { + "no-console": "off", + "indent": [ "error", 2 ], + "semi": ["error", "always"], + "linebreak-style": [ "error", "unix" ], + "comma-dangle": ["error", "always-multiline"], + "quotes": ["error", "single", { "allowTemplateLiterals": true }] + }, + "env": { + "es6": true, + "node": true, + "mocha": true, + "jasmine": true + }, + "globals": { + "angular": "true", + "window": false, + "__API_URL__": false, + "__DEBUG__": false + }, + "ecmaFeatures": { + "modules": true, + "experimentalObjectRestSpread": true, + "impliedStrict": true + }, + "extends": "eslint:recommended" +} diff --git a/lab-claudia/.gitignore b/lab-claudia/.gitignore new file mode 100644 index 0000000..e3a1c88 --- /dev/null +++ b/lab-claudia/.gitignore @@ -0,0 +1,163 @@ +#Other +db +*.env +build +coverage +html-report + +# Created by https://www.gitignore.io/api/osx,linux,vim,sublimetext,windows,node + +### 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 + + +### 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* + + +### Vim ### +# swap +[._]*.s[a-w][a-z] +[._]s[a-w][a-z] +# session +Session.vim +# temporary +.netrwhist +*~ +# auto-generated tag files +tags + + +### SublimeText ### +# cache files for sublime text +*.tmlanguage.cache +*.tmPreferences.cache +*.stTheme.cache + +# workspace files are user-specific +*.sublime-workspace + +# project files should be checked into the repository, unless a significant +# proportion of contributors will probably not be using SublimeText +# *.sublime-project + +# sftp configuration file +sftp-config.json + +# Package control specific files +Package Control.last-run +Package Control.ca-list +Package Control.ca-bundle +Package Control.system-ca-bundle +Package Control.cache/ +Package Control.ca-certs/ +bh_unicode_properties.cache + +# Sublime-github package stores a github token in this file +# https://packagecontrol.io/packages/sublime-github +GitHub.sublime-settings + + +### 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 + + +### 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 diff --git a/lab-claudia/.server.env b/lab-claudia/.server.env new file mode 100644 index 0000000..595a3cc --- /dev/null +++ b/lab-claudia/.server.env @@ -0,0 +1,12 @@ +PORT=3000 +NODE_ENV="dev" +MONGODB_URI=mongodb://localhost/art-c-dev +API_URL="http://localhost:3000" +APP_SECRET='my secret code' + +AWS_ACCESS_KEY_ID=AKIAJ6E6ZFEP6BJYUUPQ +AWS_SECRET_ACCESS_KEY=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 + +AWS_BUCKET='tastytoast-assets' +GOOGLE_CLIENT_ID="308322069962-972jlkfpbov9cd88ot0dtdtc2lgfutdl.apps.googleusercontent.com" +GOOGLE_CLIENT_SECRET="O9l0XN3xShCX-wTWS_LHv9Gf" diff --git a/lab-claudia/.travis.yml b/lab-claudia/.travis.yml new file mode 100644 index 0000000..cb34e7b --- /dev/null +++ b/lab-claudia/.travis.yml @@ -0,0 +1,20 @@ +language: node_js +node_js: + - '4.4.3' +services: + - mongodb +addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - gcc-4.8 + - g++-4.8 +env: + - CXX=g++-4.8 +sudo: required +before_script: npm i -g eslint mocha +after_success: npm run coveralls +script: + - npm test + - npm run lint diff --git a/lab-claudia/app/component/artist/artist-nav/_artist-nav.scss b/lab-claudia/app/component/artist/artist-nav/_artist-nav.scss new file mode 100644 index 0000000..1197739 --- /dev/null +++ b/lab-claudia/app/component/artist/artist-nav/_artist-nav.scss @@ -0,0 +1,15 @@ +@import "theme"; + +.artist-navbar { + background-color: $app-black; + color: $app-white; + + .artist-nav-btn { + background-color: transparent; + margin: 2%; + } + + .artist-nav-btn:hover { + color: $app-white; + } +} diff --git a/lab-claudia/app/component/artist/artist-nav/artist-nav.html b/lab-claudia/app/component/artist/artist-nav/artist-nav.html new file mode 100644 index 0000000..7f31622 --- /dev/null +++ b/lab-claudia/app/component/artist/artist-nav/artist-nav.html @@ -0,0 +1,31 @@ + diff --git a/lab-claudia/app/component/artist/artist-nav/artist-nav.js b/lab-claudia/app/component/artist/artist-nav/artist-nav.js new file mode 100644 index 0000000..e229fe7 --- /dev/null +++ b/lab-claudia/app/component/artist/artist-nav/artist-nav.js @@ -0,0 +1,24 @@ +'use strict'; + +require('./_artist-nav.scss'); + +module.exports = { + template: require('./artist-nav.html'), + controller: ['$log', '$location', artistNavController], + controllerAs: 'artistNavCtrl', +}; + +function artistNavController($log, $location) { + $log.debug('init artistNavCtrl'); + + this.viewGallery = function(){ + $log.log('navbarCtrl.viewGallery()'); + $location.url('/gallery'); + }; + + this.updateProfile = function(){ + $log.log('navbarCtrl.updateProfile()'); + $location.url('/profile'); + }; + +} diff --git a/lab-claudia/app/component/artist/create-artist/_create-artist.scss b/lab-claudia/app/component/artist/create-artist/_create-artist.scss new file mode 100644 index 0000000..d0c27dc --- /dev/null +++ b/lab-claudia/app/component/artist/create-artist/_create-artist.scss @@ -0,0 +1,75 @@ +@import "theme"; + +.create-artist-container { + + .artist-form { + background-color: $app-white; + input, + select { + width: 80%; + font-size: 100%; + background-color: transparent; + text-transform: uppercase; + border:none; + border-bottom: 1.5px solid $app-primary; + margin: 3%; + padding: 1% 0; + color: $app-white; + } + + input:focus{ + outline: none; + } + /*---- Changes Placeholder Color -----*/ + input::-webkit-input-placeholder, + textarea::-webkit-input-placeholder { + color: $app-primary; + } + + + /* General Button Styles */ + .btn { + background-color: rgba(255,255,255,.3); + border: none; + text-transform: uppercase; + border-radius: 3px; + font-size: 120%; + padding: 1.5% 2%; + margin-top: 3%; + width: 25%; + transition: all 0.3s; + color: $app-secondary; + } + + .btn:hover { + background:rgba(255,255,255,1); + color: $app-black; + } + + p { + padding: 2%; + display: inline-block; + color: $app-white; + } + + a { + display: inline-block; + font-size: 1.2em; + text-decoration: none; + } + + @media (min-width: 400px) { + width: 90%; + } + + @media (min-width: 600px) { + width: 65%; + } + + @media (min-width: 1000px) { + width: 50%; + } + } + + +} diff --git a/lab-claudia/app/component/artist/create-artist/create-artist-controller.js b/lab-claudia/app/component/artist/create-artist/create-artist-controller.js new file mode 100644 index 0000000..7c6b1a5 --- /dev/null +++ b/lab-claudia/app/component/artist/create-artist/create-artist-controller.js @@ -0,0 +1,20 @@ +'use strict'; + +require('./_create-artist.scss'); + +module.exports = { + template: require('./create-artist.html'), + controller: ['$log', 'artistService', CreateArtistController], + controllerAs: 'createArtistCtrl', +}; + +function CreateArtistController($log, artistService){ + $log.debug('init createArtistCtrl'); + + this.artist = {}; + + this.createArtist = function(){ + artistService.createArtist(this.artist); + }; + +} diff --git a/lab-claudia/app/component/artist/create-artist/create-artist.html b/lab-claudia/app/component/artist/create-artist/create-artist.html new file mode 100644 index 0000000..928d803 --- /dev/null +++ b/lab-claudia/app/component/artist/create-artist/create-artist.html @@ -0,0 +1,77 @@ +
+ +

Create your artist profile

+ +
+ +
+ +
+ +
+
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ + + + + +
diff --git a/lab-claudia/app/component/artist/edit-artist/_edit-artist.scss b/lab-claudia/app/component/artist/edit-artist/_edit-artist.scss new file mode 100644 index 0000000..4e4e39e --- /dev/null +++ b/lab-claudia/app/component/artist/edit-artist/_edit-artist.scss @@ -0,0 +1,33 @@ +@import "theme"; + +.edit-artist-container { + color: $app-black; + background-color: $app-secondary; + padding: 3%; + + .artist-form { + /*--------- Input Styles --------*/ + input, + select { + width: 100%; + font-size: 90%; + background-color: transparent; + text-transform: uppercase; + border:none; + border-bottom: 1.5px solid $app-primary; + margin: 1%; + padding: 1% 0; + color: $app-black; + } + + input:focus{ + outline: none; + } + /*---- Changes Placeholder Color -----*/ + input::-webkit-input-placeholder, + textarea::-webkit-input-placeholder { + color: $app-primary; + } + + } +} diff --git a/lab-claudia/app/component/artist/edit-artist/edit-artist.html b/lab-claudia/app/component/artist/edit-artist/edit-artist.html new file mode 100644 index 0000000..6b63684 --- /dev/null +++ b/lab-claudia/app/component/artist/edit-artist/edit-artist.html @@ -0,0 +1,71 @@ +
+ +
+ +
+
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ + + + + +
diff --git a/lab-claudia/app/component/artist/edit-artist/edit-artist.js b/lab-claudia/app/component/artist/edit-artist/edit-artist.js new file mode 100644 index 0000000..e6cf3aa --- /dev/null +++ b/lab-claudia/app/component/artist/edit-artist/edit-artist.js @@ -0,0 +1,21 @@ +'use strict'; + +require('./_edit-artist.scss'); + +module.exports = { + template: require('./edit-artist.html'), + controller: ['$log', 'artistService', EditArtistController], + controllerAs: 'editArtistCtrl', + bindings: { + // one way data binding + artist: '<', + }, +}; + +function EditArtistController($log, artistService) { + $log.debug('init editArtistCtrl'); + + this.updateArtist = function(){ + artistService.updateArtist(this.artist, this.artist._id); + }; +} diff --git a/lab-claudia/app/component/artist/profile-container/_profile-container.scss b/lab-claudia/app/component/artist/profile-container/_profile-container.scss new file mode 100644 index 0000000..761e0a9 --- /dev/null +++ b/lab-claudia/app/component/artist/profile-container/_profile-container.scss @@ -0,0 +1,6 @@ +@import "theme"; + +.profile-container { + padding: 3%; + +} diff --git a/lab-claudia/app/component/artist/profile-container/profile-container.html b/lab-claudia/app/component/artist/profile-container/profile-container.html new file mode 100644 index 0000000..12fa8ae --- /dev/null +++ b/lab-claudia/app/component/artist/profile-container/profile-container.html @@ -0,0 +1,12 @@ +
+

Edit your profile

+
+
+ +
+ +
+ +
+
+
diff --git a/lab-claudia/app/component/artist/profile-container/profile-container.js b/lab-claudia/app/component/artist/profile-container/profile-container.js new file mode 100644 index 0000000..7441575 --- /dev/null +++ b/lab-claudia/app/component/artist/profile-container/profile-container.js @@ -0,0 +1,27 @@ +'use strict'; + +require('./_profile-container.scss'); + +// Takes in a pic and a artist via one way data binding +// Bindings define the attributes 'pic' and 'artist' in the html +// Assigns 'pic' and 'artist' on the scope of the profile-container - objects can be passed in + +module.exports = { + template: require('./profile-container.html'), + controller: ['$log', 'picService', ProfileContainerController], + controllerAs: 'profileContainerCtrl', + bindings: { + pic: '<', + artist: '<', + }, +}; + +function ProfileContainerController($log, picService) { + + $log.debug('init profileContainerCtrl'); + + this.deletePic = function() { + $log.debug('profileContainerCtrl.deletePic'); + picService.deleteArtistPic(this.artist, this.pic._id); + }; +} diff --git a/lab-claudia/app/component/artist/profile-pic/_profile-pic.scss b/lab-claudia/app/component/artist/profile-pic/_profile-pic.scss new file mode 100644 index 0000000..e69de29 diff --git a/lab-claudia/app/component/artist/profile-pic/profile-pic.html b/lab-claudia/app/component/artist/profile-pic/profile-pic.html new file mode 100644 index 0000000..aa1600e --- /dev/null +++ b/lab-claudia/app/component/artist/profile-pic/profile-pic.html @@ -0,0 +1,18 @@ +
+ +
+ + +
+ + + +
+
+
diff --git a/lab-claudia/app/component/artist/profile-pic/profile-pic.js b/lab-claudia/app/component/artist/profile-pic/profile-pic.js new file mode 100644 index 0000000..7da04af --- /dev/null +++ b/lab-claudia/app/component/artist/profile-pic/profile-pic.js @@ -0,0 +1,24 @@ +'use strict'; + +require('./_profile-pic.scss'); + +module.exports = { + template: require('./profile-pic.html'), + controller: ['$log', 'picService', ProfilePicController], + controllerAs: 'profilePicCtrl', +}; + +function ProfilePicController($log, picService) { + $log.debug('init profilePicCtrl'); + this.pic = {}, + this.done = function(){ + this.pic.file = null; + }; + + this.uploadArtistPic = function(){ + picService.uploadArtistPic(this.artist, this.pic) + .then(() => { + this.done(); + }); + }; +} diff --git a/lab-claudia/app/component/gallery/create-gallery/_create-gallery.scss b/lab-claudia/app/component/gallery/create-gallery/_create-gallery.scss new file mode 100644 index 0000000..98d2cf7 --- /dev/null +++ b/lab-claudia/app/component/gallery/create-gallery/_create-gallery.scss @@ -0,0 +1,39 @@ +@import "theme"; + +.create-gallery-container { + width: 90%; + margin: 0 5%; + padding: 3%; + margin-bottom: 3%; + background-color: $app-primary; + + h2 { + color: $app-black; + margin: 3%; + padding: 1% 0; + + } + + .btn-add { + background-color: $app-secondary; + border: 1px solid $app-primary; + color: $app-black; + margin: 3%; + } + + .btn-add:hover { + background-color: $app-white; + } + + input, + select { + width: 80%; + font-size: 100%; + border: 1.5px solid $app-secondary; + border-radius: 10px; + margin: 3% 10%; + padding: 1% 2%; + color: $app-black; + } + +} diff --git a/lab-claudia/app/component/gallery/create-gallery/create-gallery.html b/lab-claudia/app/component/gallery/create-gallery/create-gallery.html new file mode 100644 index 0000000..6e1a944 --- /dev/null +++ b/lab-claudia/app/component/gallery/create-gallery/create-gallery.html @@ -0,0 +1,36 @@ + diff --git a/lab-claudia/app/component/gallery/create-gallery/create-gallery.js b/lab-claudia/app/component/gallery/create-gallery/create-gallery.js new file mode 100644 index 0000000..92c6847 --- /dev/null +++ b/lab-claudia/app/component/gallery/create-gallery/create-gallery.js @@ -0,0 +1,25 @@ +'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('init createGalleryCtrl'); + this.gallery = {}; + // method that passes in current gallery object when called from view + this.createGallery = function(){ + + galleryService.createGallery(this.gallery) + .then(() => { + // on success, nulls out inputs so it doesn't repeat the old data + // ng-model is using it, so they stay in there + this.gallery.name = null; + this.gallery.desc = null; + }); + }; +} diff --git a/lab-claudia/app/component/gallery/edit-gallery/_edit-gallery.scss b/lab-claudia/app/component/gallery/edit-gallery/_edit-gallery.scss new file mode 100644 index 0000000..829d2eb --- /dev/null +++ b/lab-claudia/app/component/gallery/edit-gallery/_edit-gallery.scss @@ -0,0 +1,21 @@ +@import "theme"; +.edit-gallery-container { + margin-bottom: 3%; + + .btn { + color: $app-black; + } + + input, + select { + width: 90%; + margin: 0 5%; + font-size: 100%; + border:none; + border: 1.5px solid $app-secondary; + margin: 3%; + padding: 1% 0; + color: $app-black; + } + +} diff --git a/lab-claudia/app/component/gallery/edit-gallery/edit-gallery.html b/lab-claudia/app/component/gallery/edit-gallery/edit-gallery.html new file mode 100644 index 0000000..dde96eb --- /dev/null +++ b/lab-claudia/app/component/gallery/edit-gallery/edit-gallery.html @@ -0,0 +1,24 @@ + diff --git a/lab-claudia/app/component/gallery/edit-gallery/edit-gallery.js b/lab-claudia/app/component/gallery/edit-gallery/edit-gallery.js new file mode 100644 index 0000000..6440047 --- /dev/null +++ b/lab-claudia/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: { + // one way data binding + gallery: '<', + }, +}; + +function EditGalleryController($log, galleryService){ + $log.debug('init editGalleryCtrl'); + + this.updateGallery = function(){ + galleryService.updateGallery(this.gallery, this.gallery._id); + }; +} diff --git a/lab-claudia/app/component/gallery/gallery-li/_gallery-li.scss b/lab-claudia/app/component/gallery/gallery-li/_gallery-li.scss new file mode 100644 index 0000000..512878a --- /dev/null +++ b/lab-claudia/app/component/gallery/gallery-li/_gallery-li.scss @@ -0,0 +1,25 @@ +@import "theme"; +@import "vendor"; + +.gallery-li { + margin: 1.6%; + padding: 2%; + width: 30%; + float: right; + background-color: $app-secondary; + + p { + margin: 2%; + } + span { + float: left; + margin: 2%; + } + .fa { + color: $app-primary; + transition: 1s; + } + .fa:hover { + color: $app-white; + } +} diff --git a/lab-claudia/app/component/gallery/gallery-li/gallery-li.html b/lab-claudia/app/component/gallery/gallery-li/gallery-li.html new file mode 100644 index 0000000..6103dfd --- /dev/null +++ b/lab-claudia/app/component/gallery/gallery-li/gallery-li.html @@ -0,0 +1,18 @@ + + diff --git a/lab-claudia/app/component/gallery/gallery-li/gallery-li.js b/lab-claudia/app/component/gallery/gallery-li/gallery-li.js new file mode 100644 index 0000000..1733154 --- /dev/null +++ b/lab-claudia/app/component/gallery/gallery-li/gallery-li.js @@ -0,0 +1,42 @@ +'use strict'; + +require('./_gallery-li.scss'); + +module.exports = { + template: require('./gallery-li.html'), + controller: ['$log', 'galleryService', GalleryLIController], + controllerAs: 'galleryLICtrl', + bindings: { + gallery: '<', + deleteDone: '&', + }, +}; + +function GalleryLIController($log, galleryService){ + $log.debug('init galleryLICtrl'); + + this.showEditGallery = false; + + this.deleteGallery = function(){ + // console.log('galleryservice.deleteGallery'); + // console.log('galleryid', this.gallery._id); + galleryService.deleteGallery(this.gallery._id) + .then(() => { + this.deleteDone({galleryData: this.gallery}); + }); + }; +} + +// the Template filename creates +// Bindings allow objects and functions to be passed through scopes in html +// & - passing in a function - allows us to pass from child scope to parent scope +// < - passing in an object - pass in from parent to child + +// GalleryLIController +// The edit gallery is not shown unless button is clicked +// DeleteGallery is called when button is clicked - delete done is passed in in the template for home ctr; +// when gal is sucessfully deleted, it calls delete done +// Object is passed in deleteDone to map named parameters in function call +// this.deleteDone - function added to the scope via binding +// & - attributes are always named properties on an object +// & creates the 'gallery' attribute used to pass a gallery in in the html diff --git a/lab-claudia/app/component/gallery/thumbnail-container/_thumbnail-container.scss b/lab-claudia/app/component/gallery/thumbnail-container/_thumbnail-container.scss new file mode 100644 index 0000000..6718c5a --- /dev/null +++ b/lab-claudia/app/component/gallery/thumbnail-container/_thumbnail-container.scss @@ -0,0 +1,28 @@ +@import "theme"; +@import "vendor"; + +.thumbnail-container { + width: 90%; + margin: 0 5%; + padding: 3%; + margin-bottom: 3%; + background-color: $app-primary; + + h2 { + color: $app-black; + margin: 3%; + padding: 1% 0; + } + + input, + select { + width: 80%; + font-size: 100%; + border: 1.5px solid $app-secondary; + border-radius: 10px; + margin: 3% 10%; + padding: 1% 2%; + color: $app-black; + } + +} diff --git a/lab-claudia/app/component/gallery/thumbnail-container/thumbnail-container.html b/lab-claudia/app/component/gallery/thumbnail-container/thumbnail-container.html new file mode 100644 index 0000000..d87630a --- /dev/null +++ b/lab-claudia/app/component/gallery/thumbnail-container/thumbnail-container.html @@ -0,0 +1,9 @@ +
+

Upload pics to {{ thumbnailContainerCtrl.gallery.name }}

+ + +
+ +
+
+ diff --git a/lab-claudia/app/component/gallery/thumbnail-container/thumbnail-container.js b/lab-claudia/app/component/gallery/thumbnail-container/thumbnail-container.js new file mode 100644 index 0000000..b00ab21 --- /dev/null +++ b/lab-claudia/app/component/gallery/thumbnail-container/thumbnail-container.js @@ -0,0 +1,14 @@ +'use strict'; + +require('./_thumbnail-container.scss'); + +module.exports = { + template: require('./thumbnail-container.html'), + controllerAs: 'thumbnailContainerCtrl', + // Takes in a gallery via one way data binding + // Binding defines an attribute 'gallery' in the html + // Assigns 'gallery' on the scope of the thumbnail-conatiner + bindings: { + gallery: '<', + }, +}; diff --git a/lab-claudia/app/component/gallery/thumbnail/_thumbnail.scss b/lab-claudia/app/component/gallery/thumbnail/_thumbnail.scss new file mode 100644 index 0000000..84c572e --- /dev/null +++ b/lab-claudia/app/component/gallery/thumbnail/_thumbnail.scss @@ -0,0 +1,9 @@ +@import "theme"; +@import "vendor"; + +.thumbnail { + img { + width: 80%; + margin: 1% 10%; + } +} diff --git a/lab-claudia/app/component/gallery/thumbnail/thumbnail.html b/lab-claudia/app/component/gallery/thumbnail/thumbnail.html new file mode 100644 index 0000000..0782394 --- /dev/null +++ b/lab-claudia/app/component/gallery/thumbnail/thumbnail.html @@ -0,0 +1,10 @@ + + + + +
+ + + {{thumbnailCtrl.pic.desc}} + +
diff --git a/lab-claudia/app/component/gallery/thumbnail/thumbnail.js b/lab-claudia/app/component/gallery/thumbnail/thumbnail.js new file mode 100644 index 0000000..3405d88 --- /dev/null +++ b/lab-claudia/app/component/gallery/thumbnail/thumbnail.js @@ -0,0 +1,26 @@ +'use strict'; + +require('./_thumbnail.scss'); + +// Takes in a pic and a gallery via one way data binding +// Bindings define the attributes 'pic' and 'gallery' in the html +// Assigns 'pic' and 'gallery' on the scope of the thumbnail-container - objects can be passed in + +module.exports = { + template: require('./thumbnail.html'), + controller: ['$log', 'picService', ThumbnailController], + controllerAs: 'thumbnailCtrl', + bindings: { + pic: '<', + gallery: '<', + }, +}; + +function ThumbnailController($log, picService) { + $log.debug('init thumbnailCtrl'); + this.deletePic = function() { + $log.debug('thumbnailCtrl.deletePic'); + // pass in the current gallery and pic id to deleteGalleryPic + picService.deleteGalleryPic(this.gallery, this.pic._id); + }; +} diff --git a/lab-claudia/app/component/gallery/upload-pic/_upload-pic.scss b/lab-claudia/app/component/gallery/upload-pic/_upload-pic.scss new file mode 100644 index 0000000..2c6da80 --- /dev/null +++ b/lab-claudia/app/component/gallery/upload-pic/_upload-pic.scss @@ -0,0 +1,44 @@ +@import "theme"; +@import "vendor"; + +.upload-pic{ + + h2 { + text-align: center; + } + form { + width: 80%; + margin: 2% 10%; + } + + .btn { + background-color: $app-secondary; + border: 1px solid $app-primary; + color: $app-black; + margin: 3%; + } + + .btn:hover { + background-color: $app-white; + } + + input, + select { + width: 90%; + margin: 0 5%; + font-size: 100%; + border:none; + border: 1.5px solid $app-secondary; + margin: 3%; + padding: 1% 0; + color: $app-black; + } + .has-error { + background-color: $app-warn; + color: $app-white; + } + .btn-success { + background-color: $app-success; + } + +} diff --git a/lab-claudia/app/component/gallery/upload-pic/upload-pic.html b/lab-claudia/app/component/gallery/upload-pic/upload-pic.html new file mode 100644 index 0000000..3a19f92 --- /dev/null +++ b/lab-claudia/app/component/gallery/upload-pic/upload-pic.html @@ -0,0 +1,26 @@ +
+ +
+ +
+
+ +
+
+ +
+
+ +
+ + + +
+
+
diff --git a/lab-claudia/app/component/gallery/upload-pic/upload-pic.js b/lab-claudia/app/component/gallery/upload-pic/upload-pic.js new file mode 100644 index 0000000..8c61573 --- /dev/null +++ b/lab-claudia/app/component/gallery/upload-pic/upload-pic.js @@ -0,0 +1,33 @@ +'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('init uploadPicCtrl'); + + this.pic = {}, + + this.done = function(){ + // this.showForm = false; + this.pic.name = null; + this.pic.desc = null; + this.pic.file = null; + }; + + this.uploadPic = function(){ + picService.uploadGalleryPic(this.gallery, this.pic) + .then(() => { + this.done(); + }); + }; + +} diff --git a/lab-claudia/app/component/landing/login/_login.scss b/lab-claudia/app/component/landing/login/_login.scss new file mode 100644 index 0000000..26d2868 --- /dev/null +++ b/lab-claudia/app/component/landing/login/_login.scss @@ -0,0 +1,5 @@ +@import "theme"; + +.form-control { + color: $app-white; +} diff --git a/lab-claudia/app/component/landing/login/login.html b/lab-claudia/app/component/landing/login/login.html new file mode 100644 index 0000000..8fd24af --- /dev/null +++ b/lab-claudia/app/component/landing/login/login.html @@ -0,0 +1,34 @@ + diff --git a/lab-claudia/app/component/landing/login/login.js b/lab-claudia/app/component/landing/login/login.js new file mode 100644 index 0000000..9748731 --- /dev/null +++ b/lab-claudia/app/component/landing/login/login.js @@ -0,0 +1,20 @@ +'use strict'; + +module.exports = { + template: require('./login.html'), + controller: ['$log', '$location', 'authService', LoginController], + controllerAs: 'loginCtrl', +}; + +function LoginController($log, $location, authService){ + $log.debug('init loginCtrl'); + // login method on loginCtrl + this.login = function(){ + $log.log('loginCtrl.login()'); + // calls authService.login given current user + authService.login(this.user) + .then(() => { + $location.url('/home'); + }); + }; +} diff --git a/lab-claudia/app/component/landing/signup/_signup.scss b/lab-claudia/app/component/landing/signup/_signup.scss new file mode 100644 index 0000000..7cde8d9 --- /dev/null +++ b/lab-claudia/app/component/landing/signup/_signup.scss @@ -0,0 +1 @@ +@import "theme"; diff --git a/lab-claudia/app/component/landing/signup/signup.html b/lab-claudia/app/component/landing/signup/signup.html new file mode 100644 index 0000000..6ebebc0 --- /dev/null +++ b/lab-claudia/app/component/landing/signup/signup.html @@ -0,0 +1,22 @@ + diff --git a/lab-claudia/app/component/landing/signup/signup.js b/lab-claudia/app/component/landing/signup/signup.js new file mode 100644 index 0000000..1f8ac22 --- /dev/null +++ b/lab-claudia/app/component/landing/signup/signup.js @@ -0,0 +1,20 @@ +'use strict'; + +module.exports = { + template: require('./signup.html'), + controller: ['$log', '$location', 'authService', SignupController], + controllerAs: 'signupCtrl', +}; + +function SignupController($log, $location, authService){ + this.signup = function(user){ + // calls authService.signup- if suceds redirect to homeview + authService.signup(user) + .then(() => { + $location.path('/home'); + }) + .catch(() => { + console.log('Signup Failed'); + }); + }; +} diff --git a/lab-claudia/app/component/navbar/_navbar.scss b/lab-claudia/app/component/navbar/_navbar.scss new file mode 100644 index 0000000..a87ac7a --- /dev/null +++ b/lab-claudia/app/component/navbar/_navbar.scss @@ -0,0 +1,48 @@ +@import "theme"; +@import "vendor"; + +.navbar { + background-color: transparent; + border:none; + height: 60px; + + .btn-logout { + display: none; + float: right; + font-size: 100%; + padding: .75% 1.25%; + margin: 1.5% 2%; + @media (min-width: 500px){ + display: inline-block; + } + } + + .navbar-brand { + color: $app-secondary; + float: left; + margin: 1.5% 2%; + font-size: 170%; + text-decoration: none; + } + + .mobile-nav-icon { + padding: .75% 1.25%; + margin: 1.5% 2%; + display: none; + float: right; + background-color: transparent; + color: $app-black; + transition: 1s; + + @media (max-width: 500px){ + display: inline-block; + } + } + + + .mobile-nav-icon:hover { + color: $app-white; + } + + +} diff --git a/lab-claudia/app/component/navbar/navbar.html b/lab-claudia/app/component/navbar/navbar.html new file mode 100644 index 0000000..312103d --- /dev/null +++ b/lab-claudia/app/component/navbar/navbar.html @@ -0,0 +1,22 @@ + + diff --git a/lab-claudia/app/component/navbar/navbar.js b/lab-claudia/app/component/navbar/navbar.js new file mode 100644 index 0000000..81ee2fc --- /dev/null +++ b/lab-claudia/app/component/navbar/navbar.js @@ -0,0 +1,62 @@ +'use strict'; + +require('./_navbar.scss'); + +module.exports = { + template: require('./navbar.html'), + controller: ['$log', '$location', '$rootScope', 'authService', NavbarController], + controllerAs: 'navbarCtrl', + bindings: { + // @ allows us to pass name in through attribute - string or boolean + appTitle: '@', // appTitle is a property that gets set on the controllers + }, +}; + +function NavbarController($log, $location, $rootScope, authService) { + $log.debug('init navbarCtrl'); + + // Nav logic specific to $location.path() + // If path is /join, hides buttons + this.checkPath = function(){ + let path = $location.path(); + if (path === '/join'){ + this.hideButtons = true; + + // If there is a token, redirect to the home page + authService.getToken() + .then(() => { + $location.url('/home'); + }); + } + + // If not /join, check if there is a token, if not, go back to the join page + if (path !== '/join'){ + this.hideButtons = false; // buttons are un-hidden + authService.getToken() + .catch(() => { + $location.url('/join#login'); + }); + } + }; + + // On pageload, call this.checkPath() + // Run on pageload, as soon as navbar is instantiated + this.checkPath(); + + // On page success page change call this.checkPath() + // Every time url is modified, or anchortag, runs checkPath + // $locationChangeSuccess is an event built into the root scope + $rootScope.$on('$locationChangeSuccess', () => { + this.checkPath(); + }); + +// on logout, redirects to /, which we configured to take you back to join + this.logout = function(){ + $log.log('navbarCtrl.logout()'); + this.hideButtons = true; + authService.logout() + .then(() => { + $location.url('/'); + }); + }; +} diff --git a/lab-claudia/app/component/searchbar/_searchbar.scss b/lab-claudia/app/component/searchbar/_searchbar.scss new file mode 100644 index 0000000..d0561e7 --- /dev/null +++ b/lab-claudia/app/component/searchbar/_searchbar.scss @@ -0,0 +1,3 @@ +@import "theme"; + + diff --git a/lab-claudia/app/component/searchbar/searchbar.html b/lab-claudia/app/component/searchbar/searchbar.html new file mode 100644 index 0000000..4e34166 --- /dev/null +++ b/lab-claudia/app/component/searchbar/searchbar.html @@ -0,0 +1,6 @@ + + + diff --git a/lab-claudia/app/component/searchbar/searchbar.js b/lab-claudia/app/component/searchbar/searchbar.js new file mode 100644 index 0000000..23818cf --- /dev/null +++ b/lab-claudia/app/component/searchbar/searchbar.js @@ -0,0 +1,12 @@ +'use strict'; + +require('./_searchbar.scss'); + +module.exports = { + template: require('./searchbar.html'), + controllerAs: 'searchbarCtrl', + bindings: { + // two way - parent to child and child to parent + searchTerm: '=', + }, +}; diff --git a/lab-claudia/app/config/log-config.js b/lab-claudia/app/config/log-config.js new file mode 100644 index 0000000..683171b --- /dev/null +++ b/lab-claudia/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-claudia/app/config/router-config.js b/lab-claudia/app/config/router-config.js new file mode 100644 index 0000000..cc3dd27 --- /dev/null +++ b/lab-claudia/app/config/router-config.js @@ -0,0 +1,34 @@ +'use strict'; + +module.exports = ['$stateProvider', '$urlRouterProvider', routerConfig]; + +function routerConfig($stateProvider, $urlRouterProvider){ + // when you go to /signup- redirects to /join#signup + // when you go to /login- redirects to /join#login + $urlRouterProvider.when('' , '/join#signup'); + $urlRouterProvider.when('/' , '/join#signup'); //used in navbar.js + $urlRouterProvider.when('/signup' , '/join#signup'); + $urlRouterProvider.when('/login' , '/join#login'); + + let states = [ + { + name: 'home', + url: '/home', + controllerAs: 'homeCtrl', + controller: 'HomeController', + template: require('../view/home/home.html'), + }, + + { + name: 'welcome', + url: '/join', + controllerAs: 'landingCtrl', + controller: 'LandingController', + template: require('../view/landing/landing.html'), + }, + ]; + + states.forEach(state => { + $stateProvider.state(state); + }); +} diff --git a/lab-claudia/app/entry.js b/lab-claudia/app/entry.js new file mode 100644 index 0000000..082b19d --- /dev/null +++ b/lab-claudia/app/entry.js @@ -0,0 +1,90 @@ +'use strict'; + +// Build sass - gets custom bootstrap and theme +require('./scss/main.scss'); + +// Require node modules +const path = require('path'); + +// Require npm modules +const angular = require('angular'); //create angular module +const camelcase = require('camelcase'); +const pascalcase = require('pascalcase'); + +// Require angular modules +// Injected in modules to do routing and bootstrap +const ngTouch = require('angular-touch'); // Dependency of bootstrap +const ngAnimate = require('angular-animate'); // Dependency of bootstrap +const uiRouter = require('angular-ui-router'); +//const uiBootstrap = require('angular-ui-bootstrap'); // Need touch and animate to use +const ngFileUpload = require('ng-file-upload'); + +// Create angular module +// Add everything we need to module +const demoApp = angular.module(camelcase(__TITLE__), [ngTouch, ngAnimate, uiRouter, ngFileUpload]); + +// create angular module +// set up $rootScope globals +demoApp.run(['$rootScope', function($rootScope){ + $rootScope.title = __TITLE__; +}]); + +// WHAT DOES REQUIRE.CONTEXT DO? +////////////////////////////////////// +// 1. Loops through directory +// 2. Gives us path +// 3. Gives us the object to export + +// BASENAME = name of file without folders or .js +// ex: start with app/goose/file.js --> returns 'file' +// true parameter -looks through sub-directories +// Require.context returns a function + +// Returns object that has files that match regex in the config directory and loads it in +// Key - absolute path of where file was found +// Context has a method called keys that returns an array of paths +// For each loops across it and gives us the path name + +// LOAD CONFIG +let context = require.context('./config/', true, /.js$/); +// Goes through config and finds files that end .js +context.keys().forEach( path => { // Gets array of paths - path is the name returned + demoApp.config(context(path)); // Gives us the object (module that is exported) +}); + +// LOAD VIEW CONTROLLERS +context = require.context('./view/', true, /.js$/); +// Looks in view for directories ending in js +context.keys().forEach( key => { + // For every key, take path, and get the base name(home-controller) + // Pascalcase turns it into HomeController + let name = pascalcase(path.basename(key, '.js')); + // Name controller based on file name + // Pass in path into context -returns what was exported + let module = context(key); // value of module.exports + demoApp.controller(name, module); +}); + +// Load services +context = require.context('./service/', true, /.js$/); +context.keys().forEach( key => { + let name = camelcase(path.basename(key, '.js')); + let module = context(key); + demoApp.service(name, module); +}); + +// Load components +context = require.context('./component/', true, /.js$/); +context.keys().forEach( key => { + let name = camelcase(path.basename(key, '.js')); + let module = context(key); + demoApp.component(name, module); +}); + +// Load filters +context = require.context('./filter/', true, /.js$/); +context.keys().forEach( key => { + let name = camelcase(path.basename(key, '.js')); + let module = context(key); + demoApp.filter(name, module); +}); diff --git a/lab-claudia/app/filter/gallery-search.js b/lab-claudia/app/filter/gallery-search.js new file mode 100644 index 0000000..eeece91 --- /dev/null +++ b/lab-claudia/app/filter/gallery-search.js @@ -0,0 +1,25 @@ +'use strict'; + +module.exports = function(){ + return function(galleries, searchTermName, searchTermDesc){ + + let fuzzyRegexName = generateFuzzyRegex(searchTermName); + let fuzzyRegexDesc = generateFuzzyRegex(searchTermDesc); + + let filteredArray = galleries.filter(gallery => { + return fuzzyRegexName.test(gallery.name.toUpperCase()); + }); + + filteredArray =filteredArray.filter(gallery => { + return fuzzyRegexDesc.test(gallery.desc.toUpperCase()); + }); + + return filteredArray; + }; +}; + +function generateFuzzyRegex(input){ + if (!input) return /.*/; + let fuzzyString = '.*' + input.toUpperCase().split('').join('.*') + '.*'; + return new RegExp(fuzzyString); +} diff --git a/lab-claudia/app/index.html b/lab-claudia/app/index.html new file mode 100644 index 0000000..7e34742 --- /dev/null +++ b/lab-claudia/app/index.html @@ -0,0 +1,16 @@ + + + + + [NuggetGram] + + + + + + + + + + + diff --git a/lab-claudia/app/scss/lib/_theme.scss b/lab-claudia/app/scss/lib/_theme.scss new file mode 100644 index 0000000..bb23208 --- /dev/null +++ b/lab-claudia/app/scss/lib/_theme.scss @@ -0,0 +1,10 @@ +$app-primary: #c4afd6; // darker purple +$app-secondary: #ddd1e7; // lighter purple +$app-disabled: #abeaeb; +$app-active: #fa7fa7; +$app-success: #a7ff7a; +$app-error: #ff7a7a; +$app-warn: #ffa77a; +$app-info: #7aa7ff; +$app-white: #fff; +$app-black: #2a2a2a; //dark grey diff --git a/lab-claudia/app/scss/lib/_vendor.scss b/lab-claudia/app/scss/lib/_vendor.scss new file mode 100644 index 0000000..69583bf --- /dev/null +++ b/lab-claudia/app/scss/lib/_vendor.scss @@ -0,0 +1,2 @@ +// font-awesome +@import "~font-awesome/scss/font-awesome.scss"; diff --git a/lab-claudia/app/scss/main.scss b/lab-claudia/app/scss/main.scss new file mode 100644 index 0000000..d8c788b --- /dev/null +++ b/lab-claudia/app/scss/main.scss @@ -0,0 +1,156 @@ +@import "theme"; +@import "vendor"; + +html, body { + width: 100%; + height: 100%; + font-family: 'Lato', sans-serif; +} + +body { + background: + linear-gradient( + rgba(85,26,139, 0.9), + rgba(0, 36, 99, 0.5) + ), + url(http://i65.tinypic.com/j183uc.jpg) no-repeat center center fixed; + //url(http://i67.tinypic.com/x45c1g.jpg); - fruits background + background-size: cover; +} + +.has-error { + color: $app-error; +} +.has-success { + color: $app-success; +} + + +/*------------- BUTTONS ---------------*/ + +/* General Button Styles */ +.btn { + background-color: rgba(255,255,255,.3); + border: none; + text-transform: uppercase; + border-radius: 3px; + padding: 1.5% 2%; + margin-top: 3%; + display: inline-block; + outline: none; + transition: all 0.3s; + color: $app-secondary; +} +.btn:hover { + background:rgba(255,255,255,1); + color: $app-black; +} +.btn:after { + content: ''; + position: absolute; + z-index: -1; + transition: all 0.3s; +} + +/* SECTIONS ============================================================================= */ +.section { + clear: both; + padding: 0px; + margin: 0px; +} + +/* --------- Clearfix ------------ */ +.clearfix {zoom: 1;} +.clearfix:after { + content: '.'; + clear: both; + display: block; + height: 0; + visibility: hidden; +} + +/*makes every element include the margin and padding you add to it */ +* { + margin:0; + padding:0; + box-sizing: border-box; +} +/* GROUPING ============================================================================= */ + +.row { + max-width: 1140px; + margin: 0 auto; + zoom: 1; /* For IE 6/7 (trigger hasLayout) */ +} + +.row:before, +.row:after { + content:""; + display:table; +} +.row:after { + clear:both; +} + +/* GRID COLUMN SETUP ==================================================================== */ +.col { + display: block; + float:left; + margin: 1% 0 1% 1.6%; +} +.col:first-child { margin-left: 0; } /* all browsers except IE6 and lower */ + + +/* REMOVE MARGINS AS ALL GO FULL WIDTH AT 480 PIXELS */ +@media only screen and (max-width: 480px) { + .col { + /*margin: 1% 0 1% 0%;*/ + margin: 0; + } +} + + +/* GRID OF TWO ============================================================================= */ +.span-2-of-2 { + width: 100%; +} +.span-1-of-2 { + width: 49.2%; +} + +/* GO FULL WIDTH AT LESS THAN 480 PIXELS */ + +@media only screen and (max-width: 480px) { + .span-2-of-2 { + width: 100%; + } + .span-1-of-2 { + width: 100%; + } +} + + +/* GRID OF THREE ============================================================================= */ +.span-3-of-3 { + width: 100%; +} +.span-2-of-3 { + width: 66.13%; +} +.span-1-of-3 { + width: 32.26%; +} + + +/* GO FULL WIDTH AT LESS THAN 480 PIXELS */ +@media only screen and (max-width: 480px) { + .span-3-of-3 { + width: 100%; + } + .span-2-of-3 { + width: 100%; + } + .span-1-of-3 { + width: 100%; + } +} diff --git a/lab-claudia/app/service/artist-service.js b/lab-claudia/app/service/artist-service.js new file mode 100644 index 0000000..68f25cc --- /dev/null +++ b/lab-claudia/app/service/artist-service.js @@ -0,0 +1,132 @@ +// post route to create a profile when you signup +// get route that gets an artist's profile by id +// put route that updates an artist's profile +// delete route that deletes a an artist profile +'use strict'; + +module.exports = ['$q', '$log', '$http', 'authService', artistService]; + +function artistService($q, $log, $http, authService){ + $log.debug('init artistService'); + let service = {}; + + service.artists = []; + + service.createArtist = function(artist){ + $log.debug('artistService.createArtist()'); + + return authService.getToken() + .then( token => { + let url = `${__API_URL__}/api/artist`; + let config = { + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + }; + return $http.post(url, artist, config); + }) + + .then( res => { + $log.log('Succesfully created artist'); + let artist = res.data; + service.artists.unshift(artist); + return artist; + }) + + .catch( err => { + $log.error(err.message); + return $q.reject(err); + }); + }; + + service.deleteArtist = function(artistID){ + return authService.getToken() + .then(token => { + let url = `${__API_URL__}/api/artist/${artistID}`; + let config = { + headers: { + Authorization: `Bearer ${token}`, + }, + }; + return $http.delete(url, config); + }) + .then(() => { + $log.log('sucessful deletion'); + for(let i = 0; i < service.artists.length; i++){ + let current = service.artists[i]; + if(current._id === artistID){ + service.artists.splice(i,1); + break; + } + } + return $q.resolve('Success!'); + }) + .catch( err => { + $log.error(err.message); + return $q.reject(err); + }); + }; + + service.fetchArtists = function(){ + $log.debug('artistService.fetchArtists()'); + return authService.getToken() + .then( token => { + let url = `${__API_URL__}/api/artist/?sort=desc`; + let config = { + headers: { + Accept: 'application/json', + Authorization: `Bearer ${token}`, + }, + }; + return $http.get(url, config); + }) + .then( res => { + $log.log('Succesfully fetched artist profiles'); + service.artists = res.data; + return service.artists; + }) + .catch(err => { + $log.error(err.message); + return $q.reject(err); + }); + }; + + service.updateArtist = function(artist, artistID){ + $log.debug('artistService.updateArtist()'); + // call authService to get token + return authService.getToken() + // returns token + .then( token => { + // sends info to server + let url = `${__API_URL__}/api/artist/${artistID}`; + let config = { + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + }; + return $http.put(url, artist, config); + }) + + .then( res => { + + for(let i = 0; i < service.artists.length; i++){ + if (service.artists[i]._id === artistID) { + service.artists[i] = res.data; + break; + } + } + $log.log('Successfuly updated artist profile'); + return $q.resolve('Updated!'); + }) + .catch(err => { + $log.error(err.message); + return $q.reject(err); + }); + }; + + return service; +} diff --git a/lab-claudia/app/service/auth-service.js b/lab-claudia/app/service/auth-service.js new file mode 100644 index 0000000..77af826 --- /dev/null +++ b/lab-claudia/app/service/auth-service.js @@ -0,0 +1,85 @@ +'use strict'; + +module.exports = ['$q', '$log', '$http', '$window', authService]; + +function authService($q, $log, $http, $window){ + $log.debug('init authService'); + // create service + let service = {}; + service.token = null; + + service.setToken = function(_token){ + $log.debug('authService.service.setToken()'); + if (! _token) + return $q.reject(new Error('no service.token')); + $window.localStorage.setItem('service.token', _token); + service.token = _token; + return $q.resolve(service.token); + }; + + service.getToken = function(){ + $log.debug('authService.getToken'); + if (service.token) return $q.resolve(service.token); + service.token = $window.localStorage.getItem('service.token'); + if (service.token) return $q.resolve(service.token); + return $q.reject(new Error('service.token not found')); + }; + + service.logout = function(){ + $log.debug('authService.logout()'); + $window.localStorage.removeItem('service.token'); + service.token = null; + return $q.resolve(); + }; + + service.signup = function(user) { + $log.debug('authService.signup()'); + let url = `${__API_URL__}/api/signup`; + console.log('signup url', url); + + let config = { + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + }; + + return $http.post(url, user, config) + .then( res => { + $log.log('success', res.data); + // res.data is the response body aka the service.token + return service.setToken(res.data); + }) + .catch(err => { + $log.error('fail', err.message); + return $q.reject(err); + }); + }; + + service.login = function(user){ + $log.debug('authService.login()'); + let url = `${__API_URL__}/api/login`; + // base64 encoded 'username:password' + 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 service.setToken(res.data); + }) + .catch( err => { + $log.error(err.message); + return $q.reject(err); + }); + }; + + // return service + return service; +} diff --git a/lab-claudia/app/service/gallery-service.js b/lab-claudia/app/service/gallery-service.js new file mode 100644 index 0000000..939eede --- /dev/null +++ b/lab-claudia/app/service/gallery-service.js @@ -0,0 +1,142 @@ +'use strict'; + +module.exports = ['$q', '$log', '$http', 'authService', galleryService]; + +function galleryService($q, $log, $http, authService){ + $log.debug('init 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('Succesfully created gallery'); + let gallery = res.data; + service.galleries.unshift(gallery); + return gallery; + }) + + .catch( err => { + $log.error(err.message); + return $q.reject(err); + }); + }; + + service.deleteGallery = function(galleryID){ + return authService.getToken() + .then(token => { + let url = `${__API_URL__}/api/gallery/${galleryID}`; + let config = { + headers: { + //don't need accept- not getting anything back from server + //Accept: 'application/json', + Authorization: `Bearer ${token}`, + }, + }; + //ajax makes request to any url + //we are pointing the url to our API + return $http.delete(url, config); + }) + .then(() => { + $log.log('sucessful deletion'); + 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 $q.resolve('success'); + }) + .catch( err => { + $log.error(err.message); + return $q.reject(err); + }); + }; + + service.fetchGalleries = function(){ + $log.debug('galleryService.fetchGalleries()'); + return authService.getToken() + .then( token => { + // __API_URL__ is set in the .env file + let url = `${__API_URL__}/api/gallery/?sort=desc`; + // can configure with querystring sort gallery/?sort=asc, sort=desc + // sorts newest to oldest + let config = { + headers: { + Accept: 'application/json', + Authorization: `Bearer ${token}`, + }, + }; + // Re-triggers the ng-repeat so all the galleries show up + // Goes from [] to [with a bunch of galleries] + return $http.get(url, config); + }) + .then( res => { + $log.log('successful fetch user galleries'); + service.galleries = res.data; + return service.galleries; + }) + .catch(err => { + $log.error(err.message); + return $q.reject(err); + }); + }; + + service.updateGallery = function(gallery, galleryID){ + $log.debug('galleryService.updateGalleries()'); + // call authService to get token + return authService.getToken() + // returns token + .then( token => { + // sends info to server + let url = `${__API_URL__}/api/gallery/${galleryID}`; + let config = { + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + }; + // ajax request + // makes request to API, parses, updates and responds + return $http.put(url, gallery, config); + }) + // get the response + .then( res => { + // iterate over gallery Controller + // update the gallery in service.galleries + for(let i = 0; i < service.galleries.length; i++){ + if (service.galleries[i]._id === galleryID) { + service.galleries[i] = res.data; + break; + } + } + // if sucessful, returns updated gallery and resolves the promise + $log.log('successful update user gallery'); + return $q.resolve('updated'); + }) + // if not, rejects the promise and logs an error + .catch(err => { + $log.error(err.message); + return $q.reject(err); + }); + }; + + return service; +} diff --git a/lab-claudia/app/service/pic-service.js b/lab-claudia/app/service/pic-service.js new file mode 100644 index 0000000..44debfc --- /dev/null +++ b/lab-claudia/app/service/pic-service.js @@ -0,0 +1,122 @@ +'use strict'; + +module.exports = ['$q','$log', '$http', 'Upload', 'authService', picService]; + +function picService($q, $log, $http, Upload, authService) { + $log.debug('init picService'); + let service = {}; + +// Route for making an upload to Gallery + service.uploadGalleryPic = function(galleryData, picData) { + $log.debug('picService.uploadGalleryPic()'); + // Return promise + return authService.getToken() + // Get token + .then( token => { + let url = `${__API_URL__}/api/gallery/${galleryData._id}/pic`; + let headers = { + Authorization: `Bearer ${token}`, + Accept: 'application/json', + }; + // Don't need key value pairs with object literals in ES6 + return Upload.upload({ + url, + headers, + method: 'POST', + // Information expected to be uploaded (required in the back-end) + // Info is being sent as multi-part form data + data: { + name: picData.name, + desc: picData.desc, + file: picData.file, + }, + }); + }) + // Get a response that returns an image + .then( res => { + // Add pic to the front of the array + galleryData.pics.unshift(res.data); + $log.log('success!\n', res.data); + return res.data; + }) + .catch( err => { + $log.error(err.message); + return $q.reject(err); + }); + }; + +// Upload an artist profile picture + service.uploadArtistPic = function(artistData, picData) { + $log.debug('picService.uploadArtistPic()'); + + return authService.getToken() + + .then( token => { + let url = `${__API_URL__}/api/artist/${artistData._id}/photo`; + let headers = { + Authorization: `Bearer ${token}`, + Accept: 'application/json', + }; + return Upload.upload({ + url, + headers, + method: 'POST', + data: { + name: picData.name, + alt: picData.alt, + file: picData.file, + }, + }); + }) + // Get a response that returns an image + .then( res => { + $log.log('success!\n', res.data); + return res.data; + }) + .catch( err => { + $log.error(err.message); + return $q.reject(err); + }); + }; + +// Route for deleting a pic - (this.gallery, this.pic._id) in controller + service.deleteGalleryPic = function(galleryData, picID) { + $log.debug('picService.deleteGalleryPic()'); + // Get a token from the auth service + return authService.getToken() + .then(token => { + // Set the url to __API_URL__/api/gallery/:galleryID/pic/:picID + let url = `${__API_URL__}/api/gallery/${galleryData._id}/pic/${picID}`; + // Set config for http headers + let config = { + // Set authorization header + headers: { + Authorization: `Bearer ${token}`, + }, + }; + // Make $http.delete request to url with config + return $http.delete(url, config); + }) + + // On success, splice the pic out of the galleryData.pics array + .then(() => { + $log.log('Deleted pic sucessfully'); + for(let i = 0; i < galleryData.pics.length; i++){ + let current = galleryData.pics[i]; + if(current._id === picID){ + galleryData.pics.splice(i,1); + break; + } + } + // Resolve undefined + return $q.resolve(undefined); + }) + // On error log error and reject error + .catch( err => { + $log.error(err.message); + return $q.reject(err); + }); + }; + + return service; +} diff --git a/lab-claudia/app/view/home/_home.scss b/lab-claudia/app/view/home/_home.scss new file mode 100644 index 0000000..849ae08 --- /dev/null +++ b/lab-claudia/app/view/home/_home.scss @@ -0,0 +1,49 @@ +@import "theme"; + +.home { + width: 100%; + margin: 0 auto; + + .searchbar-container { + width: 100%; + background-color: $app-black; + + h2 { + font-size: 80%; + text-align: center; + padding: 5%; + color: $app-white; + } + .searchbar { + input { + width: 60%; + border: 1.5px solid $app-secondary; + margin: 3%; + padding: 1% 0; + border-radius: 10px; + color: $app-black; + } + button { + width: 30%; + } + } + + ul { + padding: 0; + } + + li{ + float: left; + padding: 1%; + } + + } + + ul { + padding: 0px; + } + li { + list-style: none; + } + +} diff --git a/lab-claudia/app/view/home/home-controller.js b/lab-claudia/app/view/home/home-controller.js new file mode 100644 index 0000000..1756974 --- /dev/null +++ b/lab-claudia/app/view/home/home-controller.js @@ -0,0 +1,42 @@ +'use strict'; + +require('./_home.scss'); + +module.exports = ['$log', '$rootScope', 'galleryService', HomeController ]; + +function HomeController($log, $rootScope, galleryService){ + $log.debug('init homeCtrl'); + this.galleries = []; + + this.fetchGalleries = function(){ + galleryService.fetchGalleries() + // Fetching returns an array of galleries + // Galleries is set as a property on the home controller + .then( galleries => { + this.galleries = galleries; + // The current gallery is the first one in the index + // In the html, the current gallery is passed into + // The thumbnail-container gallery attribute + this.currentGallery = galleries[0]; + $log.log('Succesfully found gallery'); + }); + }; + +//if you pass in function(gallery, unicorn) --> object would have another property + this.galleryDeleteDone = function(gallery){ + $log.debug('init homeCtrl.galleryDeleteDone()'); + if (this.currentGallery._id == gallery._id) { + // If the current gallery matches the galleryid, it is set to null + this.currentGallery = null; + } + }; + + // When page is loaded (controller gets created), call fetchGalleries + this.fetchGalleries(); + + // Any time url changes (locationChangeSuccess), call fetchGalleries + $rootScope.$on('$locationChangeSuccess', () => { + this.fetchGalleries(); + }); + +} diff --git a/lab-claudia/app/view/home/home.html b/lab-claudia/app/view/home/home.html new file mode 100644 index 0000000..946f2f3 --- /dev/null +++ b/lab-claudia/app/view/home/home.html @@ -0,0 +1,43 @@ +
+ + + + + +
    +
  • Search Galleries:

  • +
  • +
  • +
+ +
+ +
+ +
+
+ +
+
+ +
+
+ + +
diff --git a/lab-claudia/app/view/landing/_landing.scss b/lab-claudia/app/view/landing/_landing.scss new file mode 100644 index 0000000..bfc881c --- /dev/null +++ b/lab-claudia/app/view/landing/_landing.scss @@ -0,0 +1,98 @@ +@import "theme"; + +.landing { + .join-inner-container { + padding: 5%; + margin-top: 5%; + margin-left: auto; + margin-right: auto; + width: 100%; + background-color: transparent; + border: 1.5px solid $app-primary; + border-radius: 10px; + transition: 1s; + text-transform: uppercase; + + a { + color: $app-secondary; + } + a:hover { + color: $app-white; + } + + input { + margin-top: 2%; + + &[type='submit'] { + color: $app-secondary; + } + } + + /*--------- Input Styles --------*/ + input, + select { + width: 100%; + font-size: 100%; + background-color: transparent; + text-transform: uppercase; + border:none; + border-bottom: 1.5px solid $app-primary; + margin: 3%; + padding: 1% 0; + color: $app-white; + } + + input:focus{ + outline: none; + } + /*---- Changes Placeholder Color -----*/ + input::-webkit-input-placeholder, + textarea::-webkit-input-placeholder { + color: $app-primary; + } + + + /* General Button Styles */ + .btn { + background-color: rgba(255,255,255,.3); + border: none; + text-transform: uppercase; + border-radius: 3px; + font-size: 120%; + padding: 1.5% 2%; + margin-top: 3%; + width: 25%; + transition: all 0.3s; + color: $app-secondary; + } + .btn:hover { + background:rgba(255,255,255,1); + color: $app-black; + } + + p { + padding: 2%; + display: inline-block; + color: $app-white; + } + + a { + display: inline-block; + font-size: 1.2em; + text-decoration: none; + } + + @media (min-width: 400px) { + width: 90%; + } + + @media (min-width: 600px) { + width: 65%; + } + + @media (min-width: 1000px) { + width: 50%; + } + + } +} diff --git a/lab-claudia/app/view/landing/landing-controller.js b/lab-claudia/app/view/landing/landing-controller.js new file mode 100644 index 0000000..8f03b01 --- /dev/null +++ b/lab-claudia/app/view/landing/landing-controller.js @@ -0,0 +1,44 @@ +'use strict'; + +require('./_landing.scss'); + +module.exports = ['$log', '$location', '$rootScope', 'authService', LandingController]; + +function LandingController($log, $location, $rootScope, authService){ + $log.debug('init landingCtrl'); + + let url = $location.url(); + this.showSignup = url === '/join#signup' || url === '/join'; + + let query = $location.search(); + + if(query.token){ + authService.setToken(query.token) + .then(() => { + $location.path('/#/home'); + }); + } + + $rootScope.$on('locationChangeSuccess', () => { + let query = $location.search(); + console.log('query', query); + if(query.token){ + authService.setToken(query.token) + .then(() => { + $location.path('/#/home'); + }); + } + }); + + let googleAuthBase = 'https://accounts.google.com/o/oauth2/v2/auth'; + let googleAuthResponseType = 'response_type=code'; + let googleAuthClientID = `client_id=${__GOOGLE_CLIENT_ID__}`; + let googleAuthScope = 'scope=profile%20email%20openid'; + let googleAuthRedirectURI = 'redirect_uri=http://localhost:3000/api/auth/oauth_callback'; + let googleAuthAccessType = 'access_type=offline'; + let googleAuthPrompt = 'prompt=consent'; + + this.googleAuthURL = `${googleAuthBase}?${googleAuthResponseType}&${googleAuthClientID}&${googleAuthScope}&${googleAuthRedirectURI}&${googleAuthAccessType}&${googleAuthPrompt}`; + + +} diff --git a/lab-claudia/app/view/landing/landing.html b/lab-claudia/app/view/landing/landing.html new file mode 100644 index 0000000..da06693 --- /dev/null +++ b/lab-claudia/app/view/landing/landing.html @@ -0,0 +1,30 @@ +
+ + +
+
+ +

Already a member?

+ + Login + +
+
+ + + +
+
+ +

Not a member yet?

+ + Signup + +
+
+ +
diff --git a/lab-claudia/client-test/auth-service-test.js b/lab-claudia/client-test/auth-service-test.js new file mode 100644 index 0000000..0d30678 --- /dev/null +++ b/lab-claudia/client-test/auth-service-test.js @@ -0,0 +1,105 @@ +'use strict'; + +describe('testing authService', function(){ + beforeEach(() => { + angular.mock.module('demoApp'); + angular.mock.inject((authService, $httpBackend, $q, $window, $rootScope) => { + this.authService = authService; + authService.setToken('1234'); + this.$httpBackend = $httpBackend; + this.$q = $q; + this.$window = $window; + this.$rootScope = $rootScope; + }); + }); + + afterEach(() => { + this.authService.setToken(null); + this.$window.localStorage.clear(); + }); + + describe('testing authService.setToken()', () => { + it('should return a token', () => { + this.authService.token = 'myspecialtoken'; + + this.authService.getToken() + .then(token => { + expect(token).toEqual('myspecialtoken'); + }); + + this.$rootScope.$apply(); + }); + }); + + describe('testing #getToken()', () => { + it('should return a token', () => { + + this.authService.token = null; + this.$window.localStorage.setItem('token', '1234'); + + this.authService.getToken() + .then( token => { + expect(token).toEqual('1234'); + }) + .catch( err => { + expect(err).toEqual(null); + }); + + this.$rootScope.$apply(); + }); + }); + + describe('testing authService.login(user)', () => { + it('should return a user', () => { + + let userData = { + username: 'GooseMan', + email:'bread@tasty.com', + password: '1234567', + }; + + // Basic auth says you set a base64 endcoded header with user and password + let base64 = this.$window.btoa(`${userData.username}:${userData.password}`); + // we are expecting to get this as the header + let headers = { + Accept: 'application/json', + Authorization: `Basic ${base64}`, + }; + this.$httpBackend.expectGET('http://localhost:3000/api/login', headers) + // it should respond with the correct information + // this is what the server would actually send you + .respond(200, {_id:'5678', username: userData.username, email:userData.email}); + + // make the request to the backend + this.authService.login(userData); + //flush the backend + this.$httpBackend.flush(); + }); + }); + + describe('testing authService.signup(user)', () => { + it('should return a new user', () => { + // test userData + let userData = { + username: 'GooseMan', + email:'bread@tasty.com', + }; + // Test headers + let headers = { + Accept: 'application/json', + 'Content-Type': 'application/json', + }; + + this.$httpBackend.expectPOST('http://localhost:3000/api/signup', userData, headers) + // $http needs to be flushed to mock asynchronous requests + // It accumulates requests to the server and responds to everything at once + .respond(200, {_id:'5678', username: userData.username, email:userData.email}); + + // Make the request to the server + this.authService.signup(userData); + // Flush the backend + this.$httpBackend.flush(); + }); + }); + +}); // end first describe block diff --git a/lab-claudia/client-test/create-gallery-controller-test.js b/lab-claudia/client-test/create-gallery-controller-test.js new file mode 100644 index 0000000..5cc66ab --- /dev/null +++ b/lab-claudia/client-test/create-gallery-controller-test.js @@ -0,0 +1,61 @@ +'use strict'; + +describe('testing create-gallery controller', function(){ + + beforeEach(() => { + angular.mock.module('demoApp'); + angular.mock.inject(($componentController, $rootScope, $httpBackend, authService) => { + authService.setToken('secret'); + + this.$componentController = $componentController; + this.$rootScope = $rootScope; + this.authService = authService; + this.$httpBackend = $httpBackend; + }); + }); + + afterEach(() => { + this.authService.logout(); + }); + + describe('testing #createGallery()', () => { + it('should return a created gallery', () => { + + let url = 'http://localhost:3000/api/gallery'; + + let headers = { + Authorization: 'Bearer secret', + Accept: 'application/json', + 'Content-Type': 'application/json', + }; + let galleryData = { + name: 'exampleGallery', + desc: 'stuff', + }; + + // Make a POST request to the server + this.$httpBackend.expectPOST(url, galleryData, headers) + .respond(200); + + // Create an instance of the createGalleryCtrl + let createGalleryCtrl = this.$componentController('createGallery', null); + + // this.gallery in the component is set to {} + // We pass it galleryData representing a created gallery + createGalleryCtrl.gallery = galleryData; + + // Call createGallery on instance of controller + createGalleryCtrl.createGallery(); + + // Flush the backend + this.$httpBackend.flush(); + + // Make sure that the name and desc are set to null after a gallery has been created + expect(createGalleryCtrl.gallery.name).toEqual(null); + expect(createGalleryCtrl.gallery.desc).toEqual(null); + + this.$rootScope.$apply(); + }); + }); + +}); // end first describe block diff --git a/lab-claudia/client-test/edit-gallery-controller-test.js b/lab-claudia/client-test/edit-gallery-controller-test.js new file mode 100644 index 0000000..3ad7842 --- /dev/null +++ b/lab-claudia/client-test/edit-gallery-controller-test.js @@ -0,0 +1,78 @@ +'use strict'; + +describe('testing edit-gallery controller', function(){ + + beforeEach(() => { + angular.mock.module('demoApp'); + angular.mock.inject(($componentController, $rootScope, $httpBackend, authService) => { + authService.setToken('1234'); + + this.$componentController = $componentController; + this.$rootScope = $rootScope; + this.$httpBackend = $httpBackend; + this.authService = authService; + }); + }); + afterEach(() => { + // calling logout removes token from local storage! + this.authService.logout(); + }); + + it('testing component bindings', () => { + // Set up object to be passed in + let mockBindings = { + gallery: { + name: 'goose', + desc: 'lary', + }, + }; + + // Mock the gallery and pass in name of component + // Second argument is set if we want to override locals + let editGalleryCtrl = this.$componentController('editGallery', null, mockBindings); + expect(editGalleryCtrl.gallery.name).toEqual('goose'); + expect(editGalleryCtrl.gallery.desc).toEqual('lary'); + + this.$rootScope.$apply(); + }); + + describe('testing #updateGallery', () => { + + it('should make a valid PUT request', () => { + + let url = 'http://localhost:3000/api/gallery/12345'; + // Mock headers + let headers = { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + Authorization: 'Bearer 1234', + }; + + // Mock PUT request to server + // Can put expectPUT any where before you call flush + // Backend has to be registered before you make the request + this.$httpBackend.expectPUT(url, {_id: '12345', name: 'newname', desc: 'lary'}, headers) + // expect server to respond with a 200 status code + .respond(200); + + // Mock object to be passed in via bindings + let mockBindings = { + gallery: { + _id: '12345', + name: 'goose', + desc: 'lary', + }, + }; + + // Mock component - create an instance of component with the mocked bindings + let editGalleryCtrl = this.$componentController('editGallery', null, mockBindings); + // Mock updated name + editGalleryCtrl.gallery.name = 'newname'; + // Call updateGallery() on instance of editGalleryCtrl- + editGalleryCtrl.updateGallery(); + // Flush the backend - tells httpBackend to respond to the request + this.$httpBackend.flush(); + this.$rootScope.$apply(); // + }); + }); +}); diff --git a/lab-claudia/client-test/example-test.js b/lab-claudia/client-test/example-test.js new file mode 100644 index 0000000..99c7a31 --- /dev/null +++ b/lab-claudia/client-test/example-test.js @@ -0,0 +1,7 @@ +'use strict'; + +describe('true test', function(){ + it('should pass', () => { + expect(true).toEqual(true); + }); +}); diff --git a/lab-claudia/client-test/gallery-li-controller-test.js b/lab-claudia/client-test/gallery-li-controller-test.js new file mode 100644 index 0000000..a288506 --- /dev/null +++ b/lab-claudia/client-test/gallery-li-controller-test.js @@ -0,0 +1,87 @@ +'use strict'; + +describe('testing gallery-li controller', function(){ + + beforeEach(() => { + angular.mock.module('demoApp'); + angular.mock.inject(($componentController, $rootScope, $httpBackend, authService) => { + // Need to mock a token because galleryService requires authorization to make a request + authService.setToken('secret'); + this.$componentController = $componentController; + this.$rootScope = $rootScope; + this.authService = authService; + this.$httpBackend = $httpBackend; + }); + }); + + afterEach( () => { + // If there was a request statement that nobody makes a request to, throw an error + this.$httpBackend.verifyNoOutstandingExpectation(); + // For when statements + this.$httpBackend.verifyNoOutstandingRequest(); + }); + + afterEach(() => { + // Clear the token after every test + this.authService.logout(); + }); + + describe('testing #deleteDone()', () => { + it('should call deleteDone', () => { + // mock bindings + let mockBindings = { + gallery: { + _id: '65432ONE', + name: 'Lary', + desc: 'day in the park with lary', + pics: [], + }, + // function mimics the one we are passing into deleteDone(); + // & requires a function to be passed through attribute + // they keys line up to what is passed in on the template + deleteDone: function(data){ + expect(data.galleryData._id).toEqual('65432ONE'); + }, + }; + + let galleryLICtrl = this.$componentController('galleryLi', null, mockBindings); + // object passed in should match what is in the home controller + galleryLICtrl.deleteDone({galleryData: galleryLICtrl.gallery}); + + this.$rootScope.$apply(); + }); + + it('should call deleteDone with gallery after galleryDelete', () => { + + let url = 'http://localhost:3000/api/gallery/1234567'; + // Mock headers + let headers = { + Authorization: 'Bearer secret', + Accept: 'application/json, text/plain, */*', + }; + + // set up object to be passed in + let mockBindings = { + gallery: { + _id: '1234567', + name: 'goose', + desc: 'lary', + pics: [], + }, + deleteDone: function(data) { + expect(data.galleryData._id).toEqual(mockBindings.gallery._id); + }, + }; + + this.$httpBackend.expectDELETE(url, headers).respond(204); + + let galleryLICtrl = this.$componentController('galleryLi', null, mockBindings); + galleryLICtrl.deleteGallery(); //deleteGallery calls deleteDone() + + // Flush the backend + this.$httpBackend.flush(); + this.$rootScope.$apply(); + }); + }); + +}); // end first describe block diff --git a/lab-claudia/client-test/gallery-service-test.js b/lab-claudia/client-test/gallery-service-test.js new file mode 100644 index 0000000..1e6f8f9 --- /dev/null +++ b/lab-claudia/client-test/gallery-service-test.js @@ -0,0 +1,149 @@ +'use strict'; + +// Testing if the front-end makes the correct requests to the back-end +// beforeEach that mocks the demoApp module and services +// First we mock the demoApp to get access to services +// Angular is a global, so it doesn't have to be required in (set in the .eslintrc file) +// Mock is loaded in karma config - inject is a method that can inject services +// We need to mock a token for requests to work +// We meed to mock a gallery service and back end +// Want the tests to run independently of backend +// $httpBackend - our backend has to be turned off for this to work +// expectGET(POST...)(url, [headers], [keys]) +// Expect each request to respond with expected content and headers + +describe('testing galleryService', function(){ + beforeEach(() => { + angular.mock.module('demoApp'); + angular.mock.inject((authService, galleryService, $httpBackend, $window, $rootScope) => { + this.authService = authService; + authService.setToken('1234'); + this.galleryService = galleryService; + this.$httpBackend = $httpBackend; + this.$window = $window; + this.$rootScope = $rootScope; + }); + }); + + afterEach(() => { + this.authService.setToken(null); + this.$window.localStorage.clear(); + }); + + describe('testing galleryService.createGallery()', () => { + it('should return a gallery', () => { + + // Mocking the data and headers + // When the backend makes a request, it should have this data + let galleryData = { + name: 'exampleGallery', + desc: 'stuff', + }; + let headers = { + 'Content-Type': 'application/json', + Accept: 'application/json', + Authorization: 'Bearer 1234', + }; + + // Mock the server route + // Expect that a POST request is made to the expected url with expected data and headers + this.$httpBackend.expectPOST('http://localhost:3000/api/gallery', galleryData, headers) + + // We are testing if the server responds with the correct information + // This is what the backend would actually send you + .respond(200, {_id:'5678', username:'loom', name: galleryData.name, desc: galleryData.desc, pics: []}); + + // Make the request to the backend + this.galleryService.createGallery(galleryData); + // Flush the backend + this.$httpBackend.flush(); + }); + }); + + describe('testing galleryService.deleteGallery(galleryID)', () => { + it('should succeed in deleting gallery', () => { + // 1. mock the request + let galleryID = 'helloworld'; + let headers = { + // delete route has no content type + Authorization: 'Bearer 1234', //only testing if they send us a token + Accept: 'application/json, text/plain, */*', + }; + + // 2. mock server route - use gallery/helloworld because that is the test ID we set + this.$httpBackend.expectDELETE('http://localhost:3000/api/gallery/helloworld', headers) + .respond(204); + + // 3. make request to the server + this.galleryService.deleteGallery(galleryID); + + // 4. flush the server mock + this.$httpBackend.flush(); + this.$rootScope.$apply(); + }); + }); + + describe('testing galleryService.fetchGalleries()', () => { + it('should return galleries', () => { + + // 1. Mock the request + let galleries = [ + { + name: 'gal1', + desc: 'yay', + }, + { + name: 'gal2', + desc: 'yay2', + }, + ]; + + let headers = { + Accept: 'application/json', + Authorization: 'Bearer 1234', + }; + // 2. Mock server route + this.$httpBackend.expectGET('http://localhost:3000/api/gallery/?sort=desc', headers) + + .respond(200, galleries); + + // 3. Make request + this.galleryService.fetchGalleries() + .then( galleries => { + expect(galleries.length).toEqual(2); + expect(galleries[0].name).toBe('gal1'); + }); + // 4. Flush the server mock + this.$httpBackend.flush(); + }); + }); + + describe('testing galleryService.updateGallery(galleryData, galleryID)', () => { + it('should return updated gallery', () => { + let url = 'http://localhost:3000/api/gallery/helloworld'; + // 1. Mock the request + let galleryData = { + name: 'exampleGallery', + desc: 'stuff', + }; + + let galleryID = 'helloworld'; + + let headers = { + Accept: 'application/json', + 'Content-Type': 'application/json', + Authorization: 'Bearer 1234', + }; + + // 2. Mock server route + this.$httpBackend.expectPUT(url, {name: 'exampleGallery', desc: 'stuff'}, headers) + .respond(200); + + // 3. Make request + this.galleryService.updateGallery(galleryData, galleryID); + // 4. Flush the server mock + this.$httpBackend.flush(); + }); + }); + +}); // end first describe block diff --git a/lab-claudia/client-test/home-controller-test.js b/lab-claudia/client-test/home-controller-test.js new file mode 100644 index 0000000..e69de29 diff --git a/lab-claudia/client-test/navbar-controller-test.js b/lab-claudia/client-test/navbar-controller-test.js new file mode 100644 index 0000000..aab417e --- /dev/null +++ b/lab-claudia/client-test/navbar-controller-test.js @@ -0,0 +1,20 @@ +'use strict'; + +describe('testing navbarCtrl', function(){ + beforeEach(() => { + angular.mock.module('demoApp'); + angular.mock.inject((authService, $httpBackend, $q, $window, $rootScope) => { + authService.setToken('1234'); + + this.authService = authService; + this.$httpBackend = $httpBackend; + this.$q = $q; + this.$window = $window; + this.$rootScope = $rootScope; + }); + }); + + + + +}); // end testing navbarCtrl diff --git a/lab-claudia/client-test/thumbnail-container-controller-test.js b/lab-claudia/client-test/thumbnail-container-controller-test.js new file mode 100644 index 0000000..0b41cc9 --- /dev/null +++ b/lab-claudia/client-test/thumbnail-container-controller-test.js @@ -0,0 +1,26 @@ +'use strict'; + +describe('testing thumbnail-container controller', function(){ + + beforeEach(() => { + angular.mock.module('demoApp'); + angular.mock.inject(($componentController, $rootScope) => { + this.$componentController = $componentController; + this.$rootScope = $rootScope; + }); + }); + + it('testing component bindings', () => { + let mockBindings = { + gallery: { + name: 'goose', + desc: 'lary', + }, + }; + let thumbnailContainerCtrl = this.$componentController('thumbnailContainer', null, mockBindings); + // console.log('editGalleryCtrl', editGalleryCtrl); + expect(thumbnailContainerCtrl.gallery.name).toEqual('goose'); + expect(thumbnailContainerCtrl.gallery.desc).toEqual('lary'); + this.$rootScope.$apply(); + }); +}); diff --git a/lab-claudia/client-test/thumbnail-controller-test.js b/lab-claudia/client-test/thumbnail-controller-test.js new file mode 100644 index 0000000..d0329df --- /dev/null +++ b/lab-claudia/client-test/thumbnail-controller-test.js @@ -0,0 +1,75 @@ +'use strict'; + +describe('testing thumbnail controller', function(){ + + beforeEach(() => { + angular.mock.module('demoApp'); + angular.mock.inject(($componentController, $rootScope, $httpBackend, authService) => { + authService.setToken('1234'); + + this.$componentController = $componentController; + this.$rootScope = $rootScope; + this.$httpBackend = $httpBackend; + this.authService = authService; + }); + }); + afterEach(() => { + this.authService.logout(); + }); + + + it('testing component bindings', () => { + let mockBindings = { + gallery: { + name: 'goose', + desc: 'lary', + }, + pic: { + name: 'picture', + desc: 'tasty', + }, + }; + let thumbnailCtrl = this.$componentController('thumbnail', null, mockBindings); + expect(thumbnailCtrl.gallery.name).toEqual('goose'); + expect(thumbnailCtrl.gallery.desc).toEqual('lary'); + expect(thumbnailCtrl.pic.name).toEqual('picture'); + expect(thumbnailCtrl.pic.desc).toEqual('tasty'); + this.$rootScope.$apply(); + }); + + + // describe('testing #deleteGallery', () => { + // + // it('should make a valid DELETE request', () => { + // + // let url = 'http://localhost:3000/api/pic/12345'; + // let headers = { + // Accept: 'application/json', + // 'Content-Type': 'application/json', + // Authorization: 'Bearer 1234', + // }; + // + // // Mock server route + // this.$httpBackend.expectDELETE(url, headers).respond(204); + // + // let mockBindings = { + // gallery: { + // _id: 1234, + // name: 'goose', + // desc: 'lary', + // }, + // pic: { + // _id: 12345, + // name: 'picture', + // desc: 'tasty', + // }, + // }; + // + // let thumbnailCtrl = this.$componentController('thumbnail', null, mockBindings); + // thumbnailCtrl.deletePic(); + // + // this.$httpBackend.flush(); + // this.$rootScope.$apply(); + // }); + // }); +}); diff --git a/lab-claudia/karma.conf.js b/lab-claudia/karma.conf.js new file mode 100644 index 0000000..cbb6881 --- /dev/null +++ b/lab-claudia/karma.conf.js @@ -0,0 +1,29 @@ +// Karma configuration +// Generated on Wed Oct 19 2016 09:49:07 GMT-0700 (PDT) +const webpack= require('./webpack.config.js'); +webpack.entry = {}; + +module.exports = function(config) { + config.set({ + webpack, + port: 9876, + colors: true, + basePath: '', + autoWatch: true, + singleRun: false, + concurrency: Infinity, + frameworks: ['jasmine'], + reporters: ['progress'], + browsers: ['PhantomJS'], + logLevel: config.LOG_INFO, + preprocessors: { + 'test/**/*-test.js': ['webpack'], + 'app/entry.js': ['webpack'], + }, + files: [ + 'app/entry.js', + 'test/**/*-test.js', + 'node_modules/angular-mocks/angular-mocks.js', + ], + }); +}; diff --git a/lab-claudia/lib/basic-auth-middleware.js b/lab-claudia/lib/basic-auth-middleware.js new file mode 100644 index 0000000..b6d6a58 --- /dev/null +++ b/lab-claudia/lib/basic-auth-middleware.js @@ -0,0 +1,29 @@ +'use strict'; + +const createError = require('http-errors'); +const debug = require('debug')('artc-basic-auth-middleware'); + +module.exports = function(req, res, next){ + debug(); + + var authHeader = req.headers.authorization; + if(!authHeader) + return next(createError(401, 'requires authorization header')); + + let base64String = authHeader.split('Basic ')[1]; + if(!base64String) + return next(createError(401, 'require username and password')); + + let utf8String = new Buffer(base64String, 'base64').toString(); + let authArray = utf8String.split(':'); + req.auth = { + username: authArray[0], + password: authArray[1], + }; + + if(!req.auth.username) + return next(createError(401, 'basic auth requires username')); + if(!req.auth.password) + return next(createError(401, 'basic auth requires password')); + next(); +}; diff --git a/lab-claudia/lib/bearer-auth-middleware.js b/lab-claudia/lib/bearer-auth-middleware.js new file mode 100644 index 0000000..78ca7d2 --- /dev/null +++ b/lab-claudia/lib/bearer-auth-middleware.js @@ -0,0 +1,27 @@ +'use strict'; + +const debug = require('debug')('artc:bearer-middleware'); +const jwt = require('jsonwebtoken'); +const createError = require('http-errors'); + +const User = require('../model/user.js'); + +module.exports = function(req, res, next){ + debug(); + let authHeader = req.headers.authorization; + if(!authHeader) + return next(createError(400, 'requires auth header')); + let token = authHeader.split('Bearer ')[1]; + if(!token) + return next(createError(400, 'requires token')); + + jwt.verify(token, process.env.APP_SECRET, (err, decoded) => { + if(err) return next(createError(401, 'requires valid token')); + User.findOne({findHash: decoded.token}) + .then( user => { + if (!user) next(createError(401, 'user not found or old token')); + req.user = user; + next(); + }); + }); +}; diff --git a/lab-claudia/lib/error-middleware.js b/lab-claudia/lib/error-middleware.js new file mode 100644 index 0000000..c653750 --- /dev/null +++ b/lab-claudia/lib/error-middleware.js @@ -0,0 +1,35 @@ +'use strict'; + +const createError = require('http-errors'); +const debug = require('debug')('artc:error-middleware'); + +module.exports = function(err, req, res, next){ + debug('error middleware'); + console.error(err.message); + console.error('name', err.name); + + + if (err.status){ + res.status(err.status).send(err.name); + next(); + return; + } + + if (err.name === 'ValidationError'){ + err = createError(400, err.message); + res.status(err.status).send(err.name); + next(); + return; + } + + if (err.name === 'MongoError' && err.message.includes('E11000 duplicate')){ + err = createError(409, err.message); + res.status(err.status).send(err.name); + next(); + return; + } + + err = createError(500, err.message); + res.status(err.status).send(err.name); + next(); +}; diff --git a/lab-claudia/lib/google-oauth-middleware.js b/lab-claudia/lib/google-oauth-middleware.js new file mode 100644 index 0000000..93ca180 --- /dev/null +++ b/lab-claudia/lib/google-oauth-middleware.js @@ -0,0 +1,46 @@ +'use strict'; + +const request = require('superagent'); +const debug = require('debug')('art-c:google-oauth-middleware'); + +module.exports = function(req, res, next){ + debug('getting google user info'); + if (req.query.error){ + req.googleError = new Error(req.query.error); + return next(); + } + + let data = { + code: req.query.code, + client_id: process.env.GOOGLE_CLIENT_ID, + client_secret: process.env.GOOGLE_CLIENT_SECRET, + redirect_uri: `${process.env.API_URL}/api/auth/oauth_callback`, + grant_type: 'authorization_code', + }; + + let accessToken, refreshToken, tokenTTL; + request.post('https://www.googleapis.com/oauth2/v4/token') + .type('form') + .send(data) + .then(response => { + accessToken = response.body.access_token; + refreshToken = response.body.refresh_token; + tokenTTL = response.body.expires_in; // how long the accessToken token will work in seconds + return request.get('https://www.googleapis.com/plus/v1/people/me/openIdConnect') + .set('Authorization', `Bearer ${response.body.access_token}`); + }) + .then( response => { + req.googleOAUTH = { + googleID: response.body.sub, + email: response.body.email, + accessToken, + refreshToken, + tokenTTL, + }; + next(); + }) + .catch((err) => { + req.googleError = err; + next(); + }); +}; diff --git a/lab-claudia/lib/page-query-middleware.js b/lab-claudia/lib/page-query-middleware.js new file mode 100644 index 0000000..3b2c1fc --- /dev/null +++ b/lab-claudia/lib/page-query-middleware.js @@ -0,0 +1,30 @@ +'use strict'; + +const debug = require('debug')('artc:query-middleware'); + +module.exports = function(req, res, next){ + debug('creating page queries'); + let page = 1 ; // page index to return within a collection + let offset = 0; + let pagesize = 50; + let sort = 1; + if (!isNaN(Number(req.query.page))) page = Number(req.query.page); + if (!isNaN(Number(req.query.offset))) offset = Number(req.query.offset); + if (!isNaN(Number(req.query.pagesize))) pagesize = Number(req.query.pagesize); + if (req.query.sort === 'dsc') sort = -1; // sort descending + if (req.query.sort === 'asc') sort = 1; // sort ascending + + if (page < 1) page = 1; + if (offset < 0) offset = 0; + if (pagesize < 1) pagesize = 1; + if (pagesize > 250) pagesize = 250; + + if (page > 1) + offset = (page - 1) * pagesize + offset; + // quersystring arguments + req.query.page = page; + req.query.offset = offset; + req.query.pagesize = pagesize; + req.query.sort = sort; + next(); +}; diff --git a/lab-claudia/model/artist.js b/lab-claudia/model/artist.js new file mode 100644 index 0000000..b180fe7 --- /dev/null +++ b/lab-claudia/model/artist.js @@ -0,0 +1,22 @@ +'use strict'; + +const mongoose = require('mongoose'); + +const artistSchema = mongoose.Schema({ + about: {type: String}, + phone: {type: String, unique: true}, + email: {type: String, required: true, unique: true}, + // query-sortable properties + username: {type: String, required: true, unique: true}, + created: {type: Date, required: true, default: Date.now}, + firstname: {type: String, required: true, minlength: 3}, + lastname: {type: String, required: true, minlength: 3}, + city: {type: String, required: true}, + zip: {type: String, required: true}, + // path params + userID: {type: mongoose.Schema.Types.ObjectId, required: true}, + galleries: [{type: mongoose.Schema.Types.ObjectId, ref: 'gallery'}], + photoID: {type: mongoose.Schema.Types.ObjectId}, +}); + +module.exports = mongoose.model('artist', artistSchema); diff --git a/lab-claudia/model/gallery.js b/lab-claudia/model/gallery.js new file mode 100644 index 0000000..3b11916 --- /dev/null +++ b/lab-claudia/model/gallery.js @@ -0,0 +1,19 @@ +'use strict'; + +const mongoose = require('mongoose'); + +const gallerySchema = mongoose.Schema({ + name: {type: String, required: true}, + desc: {type: String, required: true}, + // query-sortable properties + username: {type: String, required: true}, + category: {type: String, required: true}, //validated string + created: {type: Date, required: true, default: Date.now}, + // path params + userID: {type: mongoose.Schema.Types.ObjectId, required: true}, + artistID: {type: mongoose.Schema.Types.ObjectId, required: true}, + listings: [{type: mongoose.Schema.Types.ObjectId, ref: 'listing'}], + photoID: {type: mongoose.Schema.Types.ObjectId}, +}); + +module.exports = mongoose.model('gallery', gallerySchema); diff --git a/lab-claudia/model/listing.js b/lab-claudia/model/listing.js new file mode 100644 index 0000000..890addc --- /dev/null +++ b/lab-claudia/model/listing.js @@ -0,0 +1,19 @@ +'use strict'; + +const mongoose = require('mongoose'); + +const listingSchema = mongoose.Schema({ + title: {type: String, required: true}, + desc: {type: String, required: true}, + // query-sortable properties + username: {type: String, required: true}, + category: {type: String, required: true}, // validated string + created: {type: Date, required: true, default: Date.now}, + // path params + userID: {type: mongoose.Schema.Types.ObjectId, required: true}, + artistID: {type: mongoose.Schema.Types.ObjectId, required: true}, + galleryID: {type: mongoose.Schema.Types.ObjectId, required: true}, + photoID: {type: mongoose.Schema.Types.ObjectId}, +}); + +module.exports = mongoose.model('listing', listingSchema); diff --git a/lab-claudia/model/photo.js b/lab-claudia/model/photo.js new file mode 100644 index 0000000..f2d7bef --- /dev/null +++ b/lab-claudia/model/photo.js @@ -0,0 +1,18 @@ +'use strict'; + +const mongoose = require('mongoose'); + +const photoSchema = mongoose.Schema({ + name: {type: String, required: true}, + alt: {type: String, required: true}, + objectKey: {type: String, required: true, unique:true}, + imageURI: {type: String, required: true, unique:true}, + username: {type: String, required: true}, + // path params + userID: {type: mongoose.Schema.Types.ObjectId, required:true}, + artistID: {type: mongoose.Schema.Types.ObjectId}, + galleryID: {type: mongoose.Schema.Types.ObjectId}, + listingID: {type: mongoose.Schema.Types.ObjectId}, +}); + +module.exports = mongoose.model('photo', photoSchema); diff --git a/lab-claudia/model/user.js b/lab-claudia/model/user.js new file mode 100644 index 0000000..226cce3 --- /dev/null +++ b/lab-claudia/model/user.js @@ -0,0 +1,77 @@ +'use strict'; + +const bcrypt = require('bcrypt'); +const crypto = require('crypto'); +const jwt = require('jsonwebtoken'); +const Promise = require('bluebird'); +const mongoose = require('mongoose'); +const createError = require('http-errors'); +const debug = require('debug')('artc:user'); + +// module constants +const Schema = mongoose.Schema; + +const userSchema = Schema({ + username: {type: String, required: true, unique: true, minlength: 3}, + email: {type: String, required: true, unique: true}, + password: {type: String}, + findHash: {type: String, unique: true}, + google: { + googleID: {type: String}, + refreshToken: {type: String}, + accessToken: {type: String}, + tokenTimestamp: {type: Date}, + tokenTTL: {type: Number}, + }, +}); + +userSchema.methods.generatePasswordHash = function(password) { + debug('generatePasswordHash'); + return new Promise((resolve, reject) => { + bcrypt.hash(password, 10, (err, hash) => { + if (err) return reject(err); + this.password = hash; + resolve(this); + }); + }); +}; + +userSchema.methods.comparePasswordHash = function(password) { + debug('comparePasswordHash'); + return new Promise((resolve, reject) => { + bcrypt.compare(password, this.password, (err, valid) => { + if(err) return reject(err); + if(!valid) return reject(createError(401, 'wrong password')); + resolve(this); + }); + }); +}; + +userSchema.methods.generateFindHash = function() { + debug('generateFindHash'); + return new Promise((resolve, reject) => { + let tries = 0; + _generateFindHash.call(this); + function _generateFindHash() { + this.findHash = crypto.randomBytes(32).toString('hex'); + this.save() + .then(() => resolve(this.findHash)) + .catch( err => { + if (tries > 3) return reject(err); + tries++; + _generateFindHash.call(this); + }); + } + }); +}; + +userSchema.methods.generateToken = function() { + debug('generateToken'); + return new Promise((resolve, reject) => { + this.generateFindHash() + .then( findHash => resolve(jwt.sign({token: findHash}, process.env.APP_SECRET))) + .catch(err => reject(err)); + }); +}; + +module.exports = mongoose.model('user', userSchema); diff --git a/lab-claudia/package.json b/lab-claudia/package.json new file mode 100644 index 0000000..8e0c263 --- /dev/null +++ b/lab-claudia/package.json @@ -0,0 +1,86 @@ +{ + "name": "art-c", + "version": "1.0.0", + "description": "", + "main": "index.js", + "engines": { + "node": "4.4.7" + }, + "scripts": { + "build": "./node_modules/webpack/bin/webpack.js", + "build-watch": "./node_modules/webpack/bin/webpack.js -w", + "frontend-test": "./node_modules/karma/bin/karma start --single-run", + "test-watch": "./node_modules/karma/bin/karma start", + "start": "node server.js", + "start-debug": "DEBUG='art-c*' npm start", + "start-watch": "DEBUG='art-c*' ./node_modules/nodemon/bin/nodemon.js server.js", + "test": "./node_modules/istanbul/lib/cli.js cover ./node_modules/mocha/bin/_mocha", + "coveralls": "cat ./coverage/lcov.info | ./node_modules/.bin/coveralls", + "lint": "./node_modules/eslint/bin/eslint.js .", + "test-debug": "DEBUG='art-c*' ./node_modules/istanbul/lib/cli.js cover ./node_modules/mocha/bin/_mocha" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "angular": "^1.5.8", + "angular-animate": "^1.5.8", + "angular-route": "^1.5.8", + "angular-touch": "^1.5.8", + "angular-ui-bootstrap": "^2.2.0", + "angular-ui-router": "^0.3.1", + "aws-sdk": "^2.6.6", + "babel-core": "^6.17.0", + "babel-loader": "^6.2.5", + "babel-preset-es2015": "^6.16.0", + "bcrypt": "^0.8.7", + "bluebird": "^3.4.6", + "body-parser": "^1.15.2", + "bootstrap-sass": "^3.3.7", + "camelcase": "^3.0.0", + "clean-webpack-plugin": "^0.1.13", + "cors": "^2.8.1", + "css-loader": "^0.25.0", + "debug": "^2.2.0", + "del": "^2.2.2", + "dotenv": "^2.0.0", + "express": "^4.14.0", + "extract-text-webpack-plugin": "^1.0.1", + "file-loader": "^0.9.0", + "font-awesome": "^4.6.3", + "html-loader": "^0.4.4", + "html-webpack-plugin": "^2.22.0", + "http-errors": "^1.5.0", + "jsonwebtoken": "^7.1.9", + "mongoose": "^4.6.2", + "morgan": "^1.7.0", + "multer": "^1.2.0", + "node-sass": "^3.10.1", + "pascalcase": "^0.1.1", + "resolve-url-loader": "^1.6.0", + "sass-loader": "^4.0.2", + "style-loader": "^0.13.1", + "superagent": "^2.3.0", + "ui-router": "^1.0.0-alpha.3", + "url-loader": "^0.5.7", + "webpack": "^1.13.2" + }, + "devDependencies": { + "angular-mocks": "^1.5.8", + "aws-sdk-mock": "^1.5.0", + "chai": "^3.5.0", + "coveralls": "^2.11.14", + "eslint": "^3.7.1", + "istanbul": "^0.4.5", + "jasmine-core": "^2.5.2", + "karma": "^1.3.0", + "karma-jasmine": "^1.0.2", + "karma-phantomjs-launcher": "^1.0.2", + "karma-webpack": "^1.8.0", + "lorem-ipsum": "^1.0.3", + "mocha": "^3.1.0", + "ng-file-upload": "^12.2.13", + "nodemon": "^1.11.0", + "webpack-dev-server": "^1.16.2" + } +} diff --git a/lab-claudia/route/artist-router.js b/lab-claudia/route/artist-router.js new file mode 100644 index 0000000..ff0c9dd --- /dev/null +++ b/lab-claudia/route/artist-router.js @@ -0,0 +1,83 @@ +'use strict'; + +// npm modules +const Router = require('express').Router; +const jsonParser = require('body-parser').json(); +const debug = require('debug')('artc:artist-route'); +const createError = require('http-errors'); +const AWS = require('aws-sdk'); + +// app modules +const Artist = require('../model/artist.js'); +const Gallery = require('../model/gallery.js'); +const Listing = require('../model/listing.js'); +const Photo = require('../model/photo.js'); +const bearerAuth = require('../lib/bearer-auth-middleware.js'); + +AWS.config.setPromisesDependency(require('bluebird')); + +// module constants +const artistRouter = module.exports = Router(); +const s3 = new AWS.S3(); + +artistRouter.post('/api/artist', bearerAuth, jsonParser, function(req, res, next) { + debug('POST /api/artist'); + req.body.userID = req.user._id; + new Artist(req.body).save() + .then( artist => res.json(artist)) + .catch(next); +}); + +artistRouter.get('/api/artist/:artistID', bearerAuth, function(req, res, next) { + debug('GET /api/artist/:artistID'); + Artist.findById(req.params.artistID) + .populate({path: 'galleries'}) + .then( artist => { + res.json(artist); + }) + .catch( err => { + if (err.name === 'ValidationError') return next(err); + next(createError(404, err.message)); + }); +}); + +artistRouter.put('/api/artist/:artistID', bearerAuth, jsonParser, function(req, res, next) { + debug('hit route PUT /api/artist/:artistID'); + Artist.findById(req.params.artistID) + .catch(err => Promise.reject(createError(404, err.message))) + .then( artist => { + if (artist.userID.toString() !== req.user._id.toString()) + return Promise.reject(createError(401, 'invalid userid')); + return Artist.findByIdAndUpdate(req.params.artistID, req.body, {new: true, runValidators: true}); + }) + .then(artist => res.json(artist)) + .catch(next); +}); + +artistRouter.delete('/api/artist/:artistID', bearerAuth, function(req, res, next) { + debug('hit route DELETE /api/artist/:artistID'); + Artist.findById(req.params.artistID) + .catch(err => Promise.reject(createError(404, err.message))) + .then( artist => { + if (artist.userID.toString() !== req.user._id.toString()) + return Promise.reject(createError(401, 'unauthorized')); + return Artist.findByIdAndRemove(req.params.artistID); + }) + .catch( err => Promise.reject(err, err.message)) + .then( () => Listing.remove({ artistID: req.params.artistID})) + .then( () => Gallery.remove({ artistID: req.params.artistID})) + .then( () => Photo.find({ artistID: req.params.artistID})) + .then( photos => { + let s3DeletePhotoArray = []; + for(var i=0; i Photo.remove({ artistID: req.params.artistID})) + .then( () => res.sendStatus(204)) + .catch(next); +}); diff --git a/lab-claudia/route/auth-router.js b/lab-claudia/route/auth-router.js new file mode 100644 index 0000000..7cdd7fa --- /dev/null +++ b/lab-claudia/route/auth-router.js @@ -0,0 +1,161 @@ +'use strict'; + +const Router = require('express').Router; +const debug = require('debug')('artc:auth-router'); +const jsonParser = require('body-parser').json(); +const createError = require('http-errors'); +const AWS = require('aws-sdk'); + +const Artist = require('../model/artist.js'); +const Gallery = require('../model/gallery.js'); +const Listing = require('../model/listing.js'); +const Photo = require('../model/photo.js'); +const User = require('../model/user.js'); + +const googleOAUTH = require('../lib/google-oauth-middleware.js'); +const basicAuth = require('../lib/basic-auth-middleware.js'); +const bearerAuth = require('../lib/bearer-auth-middleware'); + +AWS.config.setPromisesDependency(require('bluebird')); + +const authRouter = module.exports = Router(); +const s3 = new AWS.S3(); + +authRouter.post('/api/signup', jsonParser, function(req, res, next){ + debug('hit route POST /api/signup'); + let password = req.body.password; + delete req.body.password; + let user = new User(req.body); + + if (!password) + return next(createError(400, 'requires password')); + + if (password.length < 7) + return next(createError(400, 'password must be at least 7 characters')); + + user.generatePasswordHash(password) + .then( user => user.save()) + .then( user => user.generateToken()) + .then( token => res.send(token)) + .catch(next); +}); + +authRouter.get('/api/login', basicAuth, function(req, res, next){ + debug('hit route GET /api/login'); + + User.findOne({username: req.auth.username}) + .then( user => user.comparePasswordHash(req.auth.password)) + .catch(err => Promise.reject(createError(401, err.message))) + .then( user => user.generateToken()) + .then( token => res.send(token)) + .catch(next); +}); + +authRouter.delete('/api/user/deleteAccount', bearerAuth, function(req, res, next) { + debug('hit route DELETE /api/user/deleteAccount'); + User.findByIdAndRemove(req.user._id) + .catch( err => Promise.reject(err, err.message)) + .then( () => Listing.remove({ userID: req.user._id})) + .then( () => Gallery.remove({ userID: req.user._id})) + .then( () => Artist.remove({ userID: req.user._id})) + .then( () => Photo.find({ userID: req.user._id})) + .then( photos => { + let s3DeletePhotoArray = []; + for(var i=0; i Photo.remove({ userID: req.user._id})) + .then( () => res.sendStatus(204)) + .catch(next); +}); + +authRouter.put('/api/user/updateEmail', bearerAuth, jsonParser, function(req, res, next) { + debug('hit route PUT /api/user/updateEmail'); + return User.findByIdAndUpdate(req.user._id, req.body, {new: true, runValidators: true}) + .then( user => { + res.json(user); + }) + .catch(next); +}); + +authRouter.put('/api/user/updateUsername', bearerAuth, jsonParser, function(req, res, next) { + debug('hit route PUT /api/user/updateUsername'); + return User.findByIdAndUpdate(req.user._id, req.body, {new: true, runValidators: true}) + .then( user => { + res.json(user); + }) + .catch(next); +}); + + +authRouter.put('/api/user/updatePassword', bearerAuth, jsonParser, function(req, res, next) { + debug('hit route PUT /api/user/updatePassword'); + return User.findByIdAndUpdate(req.user._id, req.body, {new: true, runValidators: true}) + .then( user => { + res.json(user); + }) + .catch(next); +}); + + +authRouter.get('/api/auth/oauth_callback', googleOAUTH, function(req, res){ + debug('GET /api/auth/oauth_callback'); + // should have either req.googleError or req.googleOAUTH + console.log('googleError', req.googleError); + console.log('googleOAUTH', req.googleOAUTH); + + // if googleError deal with google Error + if(req.googleError){ + return res.redirect('/'); + } + + // check if user allreay exists + User.findOne({email: req.googleOAUTH.email}) + .then(user => { + if (!user) return Promise.reject(new Error('user not found')); + return user; + }) + .catch(err => { + if (err.message === 'user not found'){ + let userData = { + username: req.googleOAUTH.email, + email: req.googleOAUTH.email, + google: { + googleID: req.googleOAUTH.googleID, + tokenTTL: req.googleOAUTH.tokenTTL, + tokenTimestamp: Date.now(), + refreshToken: req.googleOAUTH.refreshToken, + accessToken: req.googleOAUTH.accessToken, + }, + }; + return new User(userData).save(); + } + return Promise.reject(err); + }) + .then(user => user.generateToken()) + .then(token => { + res.redirect(`/?token=${token}`); + }) + .catch(err => { + console.error(err); + console.log('user not found'); + res.redirect('/'); + }); + //res.send('lulwat'); + //return new User(userData).save(); + //}) + //.then(user => user.generateToken()) + //.then(token => { + //res.send(token); + //}) + //.catch((err) => { + //console.error(err); + //res.send('boo hoo') + //}); + +}); diff --git a/lab-claudia/route/gallery-router.js b/lab-claudia/route/gallery-router.js new file mode 100644 index 0000000..f296625 --- /dev/null +++ b/lab-claudia/route/gallery-router.js @@ -0,0 +1,121 @@ +'use strict'; + +// npm modules +const Router = require('express').Router; +const jsonParser = require('body-parser').json(); +const debug = require('debug')('artc:gallery-route'); +const createError = require('http-errors'); +const AWS = require('aws-sdk'); + +// app modules +const Artist = require('../model/artist.js'); +const Gallery = require('../model/gallery.js'); +const Photo = require('../model/photo.js'); +const Listing = require('../model/listing.js'); +const bearerAuth = require('../lib/bearer-auth-middleware.js'); + +AWS.config.setPromisesDependency(require('bluebird')); + +// module constants +const galleryRouter = module.exports = Router(); +const s3 = new AWS.S3(); + +galleryRouter.post('/api/artist/:artistID/gallery', bearerAuth, jsonParser, function(req, res, next) { + debug('POST /api/gallery'); + let tempArtist; + let tempGallery; + Artist.findById(req.params.artistID) + .catch(err => Promise.reject(createError(404, err.message))) + .then ( artist => { + if (artist.userID.toString() !== req.user._id.toString()) + return Promise.reject(createError(401, 'invalid user')); + tempArtist = artist; + req.body.artistID = artist._id; + req.body.userID = req.user._id; + req.body.username = artist.username; + return new Gallery(req.body).save(); + }) + .then( gallery => { + tempGallery = gallery; + tempArtist.galleries.push(gallery._id); + return tempArtist.save(); + }) + .then(() => res.json(tempGallery)) + .catch(next); +}); + +galleryRouter.post('/api/gallery', bearerAuth, jsonParser, function(req, res, next){ + debug('POST /api/gallery'); + req.body.userID = req.user._id; + req.body.username = req.user.username; + new Gallery(req.body).save() + .then( gallery => res.json(gallery)) + .catch(next); +}); + + +galleryRouter.get('/api/gallery/:galleryID', bearerAuth, function(req, res, next) { + debug('GET /api/gallery/:galleryID'); + Gallery.findById(req.params.galleryID) + .populate({path: 'listings'}) + .then( gallery => { + res.json(gallery); + }) + .catch( err => { + if (err.name === 'ValidationError') return next(err); + next(createError(404, err.message)); + }); +}); + +galleryRouter.put('/api/artist/:artistID/gallery/:galleryID', bearerAuth, jsonParser, function(req, res, next) { + debug('hit route PUT /api/gallery/:galleryID'); + Gallery.findById(req.params.galleryID) + .catch(err => Promise.reject(createError(404, err.message))) + .then( gallery => { + if (gallery.userID.toString() !== req.user._id.toString()) + return next(createError(401, 'invalid userid')); + return Gallery.findByIdAndUpdate(req.params.galleryID, req.body, {new: true, runValidators: true}); + }) + .then(gallery => { + res.json(gallery); + }) + .catch(next); +}); + +galleryRouter.delete('/api/artist/:artistID/gallery/:galleryID', bearerAuth, function(req, res, next) { + debug('hit route DELETE /api/gallery/:galleryID'); + + Gallery.findById(req.params.galleryID) + .catch(err => Promise.reject(createError(404, err.message))) + .then( gallery => { + if (gallery.userID.toString() !== req.user._id.toString()) + return Promise.reject(createError(401, 'unauthorized')); + return Gallery.findByIdAndRemove(req.params.galleryID); + }) + .catch( err => Promise.reject(err)) + .then( () => Listing.remove({ galleryID: req.params.galleryID})) + .then( () => Photo.find({galleryID: req.params.galleryID})) + .then( photos => { + let s3DeletePhotoArray = []; + for(var i=0; i Photo.remove({galleryID: req.params.galleryID})) + .then( () => { + Artist.findById(req.params.artistID) + .then( artist => { + artist.galleries.forEach( gallery => { + if(artist.galleries[gallery] === req.params.galleryID) + artist.galleries.splice(artist.galleries.indexOf(gallery), 1); + }); + return artist.save(); + }); + }) + .then( () => res.sendStatus(204)) + .catch(next); +}); diff --git a/lab-claudia/route/listing-router.js b/lab-claudia/route/listing-router.js new file mode 100644 index 0000000..5041a2d --- /dev/null +++ b/lab-claudia/route/listing-router.js @@ -0,0 +1,104 @@ +'use strict'; + +// npm modules +const Router = require('express').Router; +const jsonParser = require('body-parser').json(); +const debug = require('debug')('artc:listing-route'); +const createError = require('http-errors'); +const AWS = require('aws-sdk'); + +// app modules +const Gallery = require('../model/gallery.js'); +const Photo = require('../model/photo.js'); +const Listing = require('../model/listing.js'); +const bearerAuth = require('../lib/bearer-auth-middleware.js'); + +AWS.config.setPromisesDependency(require('bluebird')); + + +// module constants +const listingRouter = module.exports = Router(); +const s3 = new AWS.S3(); + +listingRouter.post('/api/gallery/:galleryID/listing', bearerAuth, jsonParser, function(req, res, next) { + debug('POST /api/listing'); + let tempGallery, tempListing; + Gallery.findById(req.params.galleryID) + .catch(err => Promise.reject(createError(404, err.message))) + .then ((gallery) => { + if (gallery.userID.toString() !== req.user._id.toString()) + return Promise.reject(createError(401, 'invalid user')); + req.body.galleryID = gallery._id; + req.body.artistID = gallery.artistID; + req.body.userID = req.user._id; + req.body.username = req.user.username; + tempGallery = gallery; + return new Listing(req.body).save(); + }) + .then(listing => { + tempGallery.listings.push(listing._id); + tempListing = listing; + return tempGallery.save(); + }) + .then(() => res.json(tempListing)) + .catch(next); +}); + +listingRouter.get('/api/listing/:listingID', bearerAuth, function(req, res, next) { + debug('GET /api/listing/:listingID'); + Listing.findById(req.params.listingID) + .catch(err => Promise.reject(createError(404, err.message))) + .then(listing => { + res.json(listing); + }) + .catch(next); +}); + +listingRouter.put('/api/gallery/:galleryID/listing/:listingID', bearerAuth, jsonParser, function(req, res, next) { + debug('hit route PUT /api/gallery/:galleryID/listing/:listingID'); + Listing.findById(req.params.listingID) + .catch(err => Promise.reject(createError(404, err.message))) + .then( listing => { + if(listing.userID.toString() !== req.user._id.toString()) + return Promise.reject(createError(401, 'invalid userid')); + return Listing.findByIdAndUpdate(req.params.listingID, req.body, {new: true, runValidators: true}); + }) + .then(listing => { + res.json(listing); + }) + .catch(next); +}); + +listingRouter.delete('/api/gallery/:galleryID/listing/:listingID', bearerAuth, function(req, res, next) { + debug('hit route DELETE /api/gallery/:galleryID/listing/:listingID'); + Listing.findById(req.params.listingID) + .catch(err => Promise.reject(createError(404, err.message))) + .then( listing => { + if(listing.userID.toString() !== req.user._id.toString()) + return Promise.reject(createError(401, 'invalid userid')); + return Gallery.findById(req.params.galleryID); + }) + .catch( err => err.status? Promise.reject(err) : Promise.reject(createError(404, err.message))) + .then( gallery => { + gallery.listings.forEach( listing => { + if (gallery.listings[listing] === req.params.listingID) + gallery.listings.splice(gallery.listings.indexOf(listing), 1); + }); + return gallery.save(); + }) + .then( () => Photo.find({listingID: req.params.listingID})) + .then( photos => { + let s3DeletePhotoArray = []; + for(var i=0; i Photo.remove({listingID: req.params.listingID})) + .then( () => Listing.findByIdAndRemove(req.params.listingID)) + .then( () => res.sendStatus(204)) + .catch(next); +}); diff --git a/lab-claudia/route/page-router.js b/lab-claudia/route/page-router.js new file mode 100644 index 0000000..9f1a2af --- /dev/null +++ b/lab-claudia/route/page-router.js @@ -0,0 +1,35 @@ +'use strict'; + +// npm modules +const Router = require('express').Router; +const debug = require('debug')('artc:gallery-route'); +const AWS = require('aws-sdk'); + +// app modules +const Gallery = require('../model/gallery.js'); +const Listing = require('../model/listing.js'); +const pageMiddleware = require('../lib/page-query-middleware.js'); + +AWS.config.setPromisesDependency(require('bluebird')); + +// module constants +const pageRouter = module.exports = Router(); + +pageRouter.get('/api/gallery', pageMiddleware, function(req, res, next){ + debug('hit route GET /api/gallery'); + let offset = req.query.offset, pageSize = req.query.pagesize, page = req.query.page; + let skip = offset + pageSize * page ; + Gallery.find().skip(skip).limit(pageSize) + .then(galleries => res.json(galleries)) + .catch(next); +}); + +pageRouter.get('/api/listing', pageMiddleware, function(req, res, next){ + debug('hit route GET /api/listing'); + let offset = req.query.offset, pageSize = req.query.pagesize, page = req.query.page; + + let skip = offset + pageSize * page ; + Listing.find().skip(skip).limit(pageSize) + .then( listings => res.json(listings)) + .catch(next); +}); diff --git a/lab-claudia/route/photo-router.js b/lab-claudia/route/photo-router.js new file mode 100644 index 0000000..7bde649 --- /dev/null +++ b/lab-claudia/route/photo-router.js @@ -0,0 +1,264 @@ +'use strict'; + +// NODE MODULES +const fs = require('fs'); +const path = require('path'); + +// NPM MODULES +const del = require('del'); +const AWS = require('aws-sdk'); +const multer = require('multer'); +const debug = require('debug')('artc:photo-router'); +const createError = require('http-errors'); + +//APP MODULES +const Photo = require('../model/photo.js'); +const Artist = require('../model/artist.js'); +const Gallery = require('../model/gallery.js'); +const Listing = require('../model/listing.js'); +const bearerAuth = require('../lib/bearer-auth-middleware.js'); + +AWS.config.setPromisesDependency(require('bluebird')); + +// MODULE CONSTANTS +const s3 = new AWS.S3(); +const dataDir =`${__dirname}/../data`; +const upload = multer({dest: dataDir}); +const photoRouter = module.exports = require('express').Router(); + +function s3UploadPromise(params){ + return new Promise((resolve, reject) => { + s3.upload(params, (err, s3data) => { + if (err) return reject(err); + resolve(s3data); + }); + }); +} + +photoRouter.post('/api/artist/:artistID/photo', bearerAuth, upload.single('image'), function(req, res, next){ + debug('hit POST /api/artist/:artistID/photo'); + + if (!req.file) + return next(createError(400, 'no file found')); + if (!req.file.path) + return next(createError(500, 'file not saved')); + let ext = path.extname(req.file.originalname); + + let params = { + ACL: 'public-read', + Bucket: 'artc-staging-assets', + Key: `${req.file.filename}${ext}`, + Body: fs.createReadStream(req.file.path), + }; + + let tempArtist = null; + + Artist.findById(req.params.artistID) + .catch(err => Promise.reject(createError(404, err.message))) + .then(artist => { + tempArtist = artist; + return s3UploadPromise(params); + }) + .catch(err => err.status ? Promise.reject(err) : Promise.reject(createError(500, err.message))) + .then(s3data => { + del([`${dataDir}/*`]); + let photoData = { + name: req.body.name, + username: req.user.username, + alt: req.body.alt, + objectKey: s3data.Key, + imageURI: s3data.Location, + artistID: tempArtist._id, + userID: req.user._id, + }; + return new Photo(photoData).save(); + }) + .then(photo => res.json(photo)) + .catch(err => { + del([`${dataDir}/*`]); + next(err); + }); +}); + +photoRouter.delete('/api/artist/:artistID/photo/:photoID', bearerAuth, function(req, res, next){ + debug('hit DELETE /api/artist/:artistID/photo/:photoID'); + let tempPhoto; + Photo.findById(req.params.photoID) + .catch(err => Promise.reject(createError(404, err.message))) + .then( photo => { + if(photo.userID.toString() !== req.user._id.toString()) + return Promise.reject(createError(401, 'User not authorized to delete this photo')); + tempPhoto = photo; + return Artist.findById(req.params.artistID); + }) + .catch( err => err.status? Promise.reject(err) : Promise.reject(createError(404, err.message))) + .then( artist => { + artist.photoID = null; + return artist.save(); + }) + .then( () => { + let params = { + Bucket: 'artc-staging-assets', + Key: tempPhoto.objectKey, + }; + return s3.deleteObject(params).promise(); + }) + .then( () => { + return Photo.findByIdAndRemove(req.params.photoID); + }) + .then(() => res.sendStatus(204)) + .catch(next); +}); + +photoRouter.post('/api/gallery/:galleryID/photo', bearerAuth, upload.single('image'), function(req, res, next){ + debug('hit POST /api/gallery/:galleryID/photo'); + + if (!req.file) + return next(createError(400, 'no file found')); + if (!req.file.path) + return next(createError(500, 'file not saved')); + let ext = path.extname(req.file.originalname); + + let params = { + ACL: 'public-read', + Bucket: 'artc-staging-assets', + Key: `${req.file.filename}${ext}`, + Body: fs.createReadStream(req.file.path), + }; + + let tempGallery = null; + + Gallery.findById(req.params.galleryID) + .catch(err => Promise.reject(createError(404, err.message))) + .then(gallery => { + tempGallery = gallery; + return s3UploadPromise(params); + }) + .catch(err => err.status ? Promise.reject(err) : Promise.reject(createError(500, err.message))) + .then(s3data => { + del([`${dataDir}/*`]); + let photoData = { + name: req.body.name, + username: req.user.username, + alt: req.body.alt, + objectKey: s3data.Key, + imageURI: s3data.Location, + galleryID: tempGallery._id, + artistID: tempGallery.artistID, + userID: req.user._id, + }; + return new Photo(photoData).save(); + }) + .then(photo => res.json(photo)) + .catch(err => { + del([`${dataDir}/*`]); + next(err); + }); +}); + +photoRouter.delete('/api/gallery/:galleryID/photo/:photoID', bearerAuth, function(req, res, next){ + debug('hit DELETE /api/gallery/:galleryID/photo/:photoID'); + let tempPhoto; + Photo.findById(req.params.photoID) + .catch(err => Promise.reject(createError(404, err.message))) + .then( photo => { + if(photo.userID.toString() !== req.user._id.toString()) + return Promise.reject(createError(401, 'User not authorized to delete this photo')); + tempPhoto = photo; + return Gallery.findById(req.params.galleryID); + }) + .catch( err => err.status? Promise.reject(err) : Promise.reject(createError(404, err.message))) + .then( gallery => { + gallery.photoID = null; + return gallery.save(); + }) + .then( () => { + let params = { + Bucket: 'artc-staging-assets', + Key: tempPhoto.objectKey, + }; + return s3.deleteObject(params).promise(); + }) + .then( () => { + return Photo.findByIdAndRemove(req.params.photoID); + }) + .then(() => res.sendStatus(204)) + .catch(next); +}); + +photoRouter.post('/api/listing/:listingID/photo', bearerAuth, upload.single('image'), function(req, res, next){ + debug('hit POST /api/listing/:listingID/photo'); + + if (!req.file) + return next(createError(400, 'no file found')); + if (!req.file.path) + return next(createError(500, 'file not saved')); + let ext = path.extname(req.file.originalname); + + let params = { + ACL: 'public-read', + Bucket: 'artc-staging-assets', + Key: `${req.file.filename}${ext}`, + Body: fs.createReadStream(req.file.path), + }; + + let tempListing = null; + + Listing.findById(req.params.listingID) + .catch(err => Promise.reject(createError(404, err.message))) + .then(listing => { + tempListing = listing; + return s3UploadPromise(params); + }) + .catch(err => err.status ? Promise.reject(err) : Promise.reject(createError(500, err.message))) + .then(s3data => { + del([`${dataDir}/*`]); + let photoData = { + name: req.body.name, + username: req.user.username, + alt: req.body.alt, + objectKey: s3data.Key, + imageURI: s3data.Location, + listingID: tempListing._id, + artistID: tempListing.artistID, + galleryID: tempListing.galleryID, + userID: req.user._id, + }; + return new Photo(photoData).save(); + }) + .then(photo => res.json(photo)) + .catch(err => { + del([`${dataDir}/*`]); + next(err); + }); +}); + +photoRouter.delete('/api/listing/:listingID/photo/:photoID', bearerAuth, function(req, res, next){ + debug('hit DELETE /api/listing/:listingID/photo/:photoID'); + let tempPhoto; + Photo.findById(req.params.photoID) + .catch(err => Promise.reject(createError(404, err.message))) + .then( photo => { + if(photo.userID.toString() !== req.user._id.toString()) + return Promise.reject(createError(401, 'User not authorized to delete this photo')); + tempPhoto = photo; + return Listing.findById(req.params.listingID); + }) + .catch( err => err.status? Promise.reject(err) : Promise.reject(createError(404, err.message))) + .then( listing => { + listing.photoID = null; + return listing.save(); + }) + .then( () => { + let params = { + Bucket: 'artc-staging-assets', + Key: tempPhoto.objectKey, + }; + return s3.deleteObject(params).promise(); + }) + .then( () => { + return Photo.findByIdAndRemove(req.params.photoID); + }) + .then(() => res.sendStatus(204)) + .catch(next); +}); diff --git a/lab-claudia/server.js b/lab-claudia/server.js new file mode 100644 index 0000000..840527f --- /dev/null +++ b/lab-claudia/server.js @@ -0,0 +1,55 @@ +'use strict'; + +// npm modules +const express = require('express'); +const morgan = require('morgan'); +const Promise = require('bluebird'); +const cors = require('cors'); +const mongoose = require('mongoose'); +const debug = require('debug')('artc:server'); +const dotenv = require('dotenv'); + +// app modules +const authRouter = require('./route/auth-router.js'); +const artistRouter = require('./route/artist-router.js'); +const galleryRouter = require('./route/gallery-router.js'); +const listingRouter = require('./route/listing-router.js'); +const photoRouter = require('./route/photo-router.js'); +const pageRouter = require('./route/page-router.js'); + +const errorMiddleware = require('./lib/error-middleware.js'); + +// Load server environment variables +dotenv.load({path: `${__dirname}/.server.env`}); + +// Connect to mongo database +mongoose.Promise = Promise; +mongoose.connect(process.env.MONGODB_URI); + +// Module constants +const PORT = process.env.PORT; +const app = express(); + +// app middleware +app.use(cors()); +let production = process.env.NODE_ENV === 'production'; +let morganFormat = production ? 'common' : 'dev'; +app.use(morgan(morganFormat)); + +// app routes +app.use(express.static(`${__dirname}/build`)); +app.use(authRouter); +app.use(artistRouter); +app.use(galleryRouter); +app.use(listingRouter); +app.use(photoRouter); +app.use(pageRouter); +app.use(errorMiddleware); + +// start server +const server = module.exports = app.listen(PORT, function() { + debug('server started'); + console.log('server up'); +}); + +server.isRunning = true; diff --git a/lab-claudia/test/artist-router-test.js b/lab-claudia/test/artist-router-test.js new file mode 100644 index 0000000..fc312bf --- /dev/null +++ b/lab-claudia/test/artist-router-test.js @@ -0,0 +1,1247 @@ +'use strict'; + +require('./lib/test-env.js'); +require('./lib/aws-mock.js'); + +// npm modules +const expect = require('chai').expect; +const request = require('superagent'); +const Promise = require('bluebird'); +const mongoose = require('mongoose'); + +// app modules +const serverCtrl = require('./lib/server-control'); +const cleanDB = require('./lib/clean-db'); +const mockUser = require('./lib/user-mock'); +const mockArtist = require('./lib/artist-mock'); +const mockMultipleGalleries = require('./lib/populate-artist-galleries-mock.js'); +const mockManyPhotos = require('./lib/mock-many-photos.js'); + +mongoose.Promise = Promise; + +// module constants +const server = require('../server.js'); +const url = `http://localhost:${process.env.PORT}`; + +const exampleArtist = { + firstname: 'Jimbob', + lastname: 'Jimbobberson', + username: 'Jimbobguy316', + email: 'jimbobguy14@stuff.com', + city: 'Dallas', + zip: '98114', + about: 'I\m just a simple kinda man who likes to do art stuff.', + phone: '(555)555-5555', +}; + +describe('testing artist-router', function() { + + before( done => serverCtrl.serverUp(server, done)); + after( done => serverCtrl.serverDown(server, done)); + afterEach( done => cleanDB(done)); + + describe('testing POST /api/artist', function() { + + describe('with a valid body', function() { + + before(done => mockUser.call(this, done)); + + it('should return an artist profile and a status 200', (done) => { + request.post(`${url}/api/artist`) + .send(exampleArtist) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) return done(err); + expect(res.status).to.equal(200); + expect(res.body.firstname).to.equal(exampleArtist.firstname); + expect(res.body.lastname).to.equal(exampleArtist.lastname); + expect(res.body.username).to.equal(exampleArtist.username); + expect(res.body.email).to.equal(exampleArtist.email); + expect(res.body.city).to.equal(exampleArtist.city); + expect(res.body.zip).to.equal(exampleArtist.zip); + expect(res.body.about).to.equal(exampleArtist.about); + expect(res.body.phone).to.equal(exampleArtist.phone); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + let date = new Date(res.body.created).toString(); + expect(date).to.not.equal('Invalid Date'); + done(); + }); + }); + + }); + + describe('with no firstname', function() { + + before(done => mockUser.call(this, done)); + + it('should status 400 bad request', (done) => { + request.post(`${url}/api/artist`) + .send({ + lastname: exampleArtist.lastname, + username: exampleArtist.username, + email: exampleArtist.email, + city: exampleArtist.city, + zip: exampleArtist.zip, + about: exampleArtist.about, + phone: exampleArtist.phone, + }) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + + }); + + describe('with no lastname', function() { + + before(done => mockUser.call(this, done)); + + it('should status 400 bad request', (done) => { + request.post(`${url}/api/artist`) + .send({ + firstname: exampleArtist.firstname, + username: exampleArtist.username, + email: exampleArtist.email, + city: exampleArtist.city, + zip: exampleArtist.zip, + about: exampleArtist.about, + phone: exampleArtist.phone, + }) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + + }); + + describe('with no username', function() { + + before(done => mockUser.call(this, done)); + + it('should status 400 bad request', (done) => { + + request.post(`${url}/api/artist`) + .send({ + firstname: exampleArtist.firstname, + lastname: exampleArtist.lastname, + email: exampleArtist.email, + city: exampleArtist.city, + zip: exampleArtist.zip, + about: exampleArtist.about, + phone: exampleArtist.phone, + }) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('with no email', function() { + + before(done => mockUser.call(this, done)); + + it('should return an artist profile and a status 400', (done) => { + request.post(`${url}/api/artist`) + .send({ + firstname: exampleArtist.firstname, + lastname: exampleArtist.lastname, + username: exampleArtist.username, + city: exampleArtist.city, + zip: exampleArtist.zip, + about: exampleArtist.about, + phone: exampleArtist.phone, + }) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + + }); + + describe('with no city', function() { + + before(done => mockUser.call(this, done)); + + it('should return an artist profile and a status 400', (done) => { + request.post(`${url}/api/artist`) + .send({ + firstname: exampleArtist.firstname, + lastname: exampleArtist.lastname, + username: exampleArtist.username, + email: exampleArtist.email, + zip: exampleArtist.zip, + about: exampleArtist.about, + phone: exampleArtist.phone, + }) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + + }); + + describe('with no zip', function() { + + before(done => mockUser.call(this, done)); + + it('should return an artist profile and a status 400', (done) => { + request.post(`${url}/api/artist`) + .send({ + firstname: exampleArtist.firstname, + lastname: exampleArtist.lastname, + username: exampleArtist.username, + email: exampleArtist.email, + city: exampleArtist.city, + about: exampleArtist.about, + phone: exampleArtist.phone, + }) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + + }); + + describe('with invalid date--string', function() { + + before(done => mockUser.call(this, done)); + + it('should return an artist profile and a status 400', (done) => { + request.post(`${url}/api/artist`) + .send({ + firstname: exampleArtist.firstname, + lastname: exampleArtist.lastname, + username: exampleArtist.username, + email: exampleArtist.email, + city: exampleArtist.city, + zip: exampleArtist.zip, + about: exampleArtist.about, + phone: exampleArtist.phone, + created: 'striiiing', + }) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + + }); + + describe('with an invalid body', function() { + + before(done => mockUser.call(this, done)); + + it('should a status 400 bad request', (done) => { + request.post(`${url}/api/artist`) + .send('badbody') + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + + }); + + describe('with a bad authorization header', function() { + + before(done => mockUser.call(this, done)); + + it('should return status 400 bad request', (done) => { + request.post(`${url}/api/artist`) + .send(exampleArtist) + .set({ + Authorization: 'bad request', + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + + }); + + describe('with no authorization header', function() { + + before(done => mockUser.call(this, done)); + + it('should return status 400 bad request', (done) => { + request.post(`${url}/api/artist`) + .send(exampleArtist) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + + }); + + describe('with bearer header with no token', function() { + + before(done => mockUser.call(this, done)); + + it('should return status 400 bad request', (done) => { + request.post(`${url}/api/artist`) + .send(exampleArtist) + .set({ + Authorization: 'Bearer ', + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('with already-existing username', function() { + + before( done => mockArtist.call(this, done)); + + it('should return a status 409', (done) => { + request.post(`${url}/api/artist`) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .send({ + firstname: exampleArtist.firstname, + lastname: exampleArtist.lastname, + username: this.tempArtist.username, + email: exampleArtist.email, + city: exampleArtist.city, + zip: exampleArtist.zip, + about: exampleArtist.about, + phone: this.tempArtist.phone, + }) + .end((err, res) => { + expect(res.status).to.equal(409); + expect(res.text).to.equal('ConflictError'); + done(); + }); + }); + }); + + describe('with already-existing email', function() { + + before( done => mockArtist.call(this, done)); + + it('should return a status 409', (done) => { + request.post(`${url}/api/artist`) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .send({ + firstname: exampleArtist.firstname, + lastname: exampleArtist.lastname, + username: exampleArtist.phone, + email: this.tempArtist.email, + city: exampleArtist.city, + zip: exampleArtist.zip, + about: exampleArtist.about, + phone: exampleArtist.phone, + }) + .end((err, res) => { + expect(res.status).to.equal(409); + expect(res.text).to.equal('ConflictError'); + done(); + }); + }); + + }); + + describe('with already-existing phone number', function() { + + before( done => mockArtist.call(this, done)); + + it('should return a status 409', (done) => { + request.post(`${url}/api/artist`) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .send({ + firstname: exampleArtist.firstname, + lastname: exampleArtist.lastname, + username: exampleArtist.phone, + email: exampleArtist.email, + city: exampleArtist.city, + zip: exampleArtist.zip, + about: exampleArtist.about, + phone: this.tempArtist.phone, + }) + .end((err, res) => { + expect(res.status).to.equal(409); + expect(res.text).to.equal('ConflictError'); + done(); + }); + }); + }); + + describe('with wrong token', function() { + let tempSecondUser = {}; + before(done => mockArtist.call(this, done)); + before(done => mockUser.call(tempSecondUser, done)); + + it('should return status 401 unauthorized', done => { + request.delete(`${url}/api/artist/${this.tempArtist._id}`) + .set({ + Authorization: `Bearer ${this.tempUser.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(401); + done(); + }); + }); + }); + }); + + describe('testing GET to /api/artist/:artistID', () => { + + describe('with valid token and id', function(){ + + before(done => mockArtist.call(this, done)); + + it('should return a artist', done => { + request.get(`${url}/api/artist/${this.tempArtist._id}`) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) + return done(err); + expect(res.status).to.equal(200); + expect(res.body.firstname).to.equal(this.tempArtist.firstname); + expect(res.body.lastname).to.equal(this.tempArtist.lastname); + expect(res.body.username).to.equal(this.tempArtist.username); + expect(res.body.email).to.equal(this.tempArtist.email); + expect(res.body.city).to.equal(this.tempArtist.city); + expect(res.body.zip).to.equal(this.tempArtist.zip); + expect(res.body.about).to.equal(this.tempArtist.about); + expect(res.body.phone).to.equal(this.tempArtist.phone); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + let date = new Date(res.body.created).toString(); + expect(date).to.not.equal('Invalid Date'); + done(); + }); + }); + }); + + describe('testing populate artist galleries with valid id and token', function(){ + + before(done => mockMultipleGalleries.call(this, 10, done)); + + it('should return an artist with populated gallery array', done => { + request.get(`${url}/api/artist/${this.tempArtist._id}`) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) return done(err); + expect(res.status).to.equal(200); + expect(res.body.galleries.length).to.equal(10); + done(); + }); + }); + }); + + describe('testing populate artist galleries with valid id and invalid token', function(){ + + before(done => mockMultipleGalleries.call(this, 10, done)); + + it('should return a 400 error for bad request', done => { + request.get(`${url}/api/artist/${this.tempArtist._id}`) + .set({ + Authorization: 'Bearer ', + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('testing populate artist galleries with invalid id and valid token', function(){ + + before(done => mockMultipleGalleries.call(this, 10, done)); + + it('should return an artist with populated gallery array', done => { + request.get(`${url}/api/artist/${this.tempArtist._id}bad`) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(404); + done(); + }); + }); + }); + + describe('with valid token and invalid id', function(){ + + before(done => mockArtist.call(this, done)); + + it('should status 404 not found', done => { + request.get(`${url}/api/artist/${this.tempArtist._id}bad`) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(404); + done(); + }); + }); + }); + + describe('with invalid token and valid id', function(){ + + before(done => mockArtist.call(this, done)); + + it('should return status 400 for bad request', done => { + request.get(`${url}/api/artist/${this.tempArtist._id}`) + .set({ + Authorization: 'Bearer ', + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('with no auth header', function(){ + + before(done => mockArtist.call(this, done)); + before(done => mockUser.call(this, done)); + + it('should status 400 bad request', done => { + request.get(`${url}/api/artist/${this.tempArtist._id}`) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('with wrong token', function() { + let tempSecondUser = {}; + before(done => mockArtist.call(this, done)); + before(done => mockUser.call(tempSecondUser, done)); + + it('should return status 401 unauthorized', done => { + request.delete(`${url}/api/artist/${this.tempArtist._id}`) + .set({ + Authorization: `Bearer ${this.tempUser.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(401); + done(); + }); + }); + }); + }); + + describe('testing PUT to /api/artist/:artistID', () => { + + describe('with valid token and id', function() { + + before(done => mockArtist.call(this, done)); + + it('updating only the firstname, should return an artist', done => { + let updateData = {firstname: 'bob'}; + request.put(`${url}/api/artist/${this.tempArtist._id}`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) + return done(err); + expect(res.status).to.equal(200); + expect(res.body.firstname).to.equal(updateData.firstname); + expect(res.body.lastname).to.equal(this.tempArtist.lastname); + expect(res.body.username).to.equal(this.tempArtist.username); + expect(res.body.email).to.equal(this.tempArtist.email); + expect(res.body.city).to.equal(this.tempArtist.city); + expect(res.body.zip).to.equal(this.tempArtist.zip); + expect(res.body.about).to.equal(this.tempArtist.about); + expect(res.body.phone).to.equal(this.tempArtist.phone); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + let date = new Date(res.body.created).toString(); + expect(date).to.not.equal('Invalid Date'); + done(); + }); + }); + }); + + describe('with valid token and id', function(){ + + before(done => mockArtist.call(this, done)); + + it('updating only the lastname, should return an artist', done => { + let updateData = {lastname: 'bob'}; + request.put(`${url}/api/artist/${this.tempArtist._id}`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) + return done(err); + expect(res.status).to.equal(200); + expect(res.body.firstname).to.equal(this.tempArtist.firstname); + expect(res.body.lastname).to.equal(updateData.lastname); + expect(res.body.username).to.equal(this.tempArtist.username); + expect(res.body.email).to.equal(this.tempArtist.email); + expect(res.body.city).to.equal(this.tempArtist.city); + expect(res.body.zip).to.equal(this.tempArtist.zip); + expect(res.body.about).to.equal(this.tempArtist.about); + expect(res.body.phone).to.equal(this.tempArtist.phone); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + let date = new Date(res.body.created).toString(); + expect(date).to.not.equal('Invalid Date'); + done(); + }); + }); + + describe('with valid token and id', function(){ + + before(done => mockArtist.call(this, done)); + + it('updating only the email, should return an artist', done => { + let updateData = {email: 'bob'}; + request.put(`${url}/api/artist/${this.tempArtist._id}`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) + return done(err); + expect(res.status).to.equal(200); + expect(res.body.firstname).to.equal(this.tempArtist.firstname); + expect(res.body.lastname).to.equal(this.tempArtist.lastname); + expect(res.body.username).to.equal(this.tempArtist.username); + expect(res.body.email).to.equal(updateData.email); + expect(res.body.city).to.equal(this.tempArtist.city); + expect(res.body.zip).to.equal(this.tempArtist.zip); + expect(res.body.about).to.equal(this.tempArtist.about); + expect(res.body.phone).to.equal(this.tempArtist.phone); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + let date = new Date(res.body.created).toString(); + expect(date).to.not.equal('Invalid Date'); + done(); + }); + }); + }); + + describe('with valid token and id', function() { + + before(done => mockArtist.call(this, done)); + + it('updating only the city, should return an artist', done => { + let updateData = {city: 'bob'}; + request.put(`${url}/api/artist/${this.tempArtist._id}`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) + return done(err); + expect(res.status).to.equal(200); + expect(res.body.firstname).to.equal(this.tempArtist.firstname); + expect(res.body.lastname).to.equal(this.tempArtist.lastname); + expect(res.body.username).to.equal(this.tempArtist.username); + expect(res.body.email).to.equal(this.tempArtist.email); + expect(res.body.city).to.equal(updateData.city); + expect(res.body.zip).to.equal(this.tempArtist.zip); + expect(res.body.about).to.equal(this.tempArtist.about); + expect(res.body.phone).to.equal(this.tempArtist.phone); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + let date = new Date(res.body.created).toString(); + expect(date).to.not.equal('Invalid Date'); + done(); + }); + }); + }); + + describe('with valid token and id', function() { + + before(done => mockArtist.call(this, done)); + + it('updating only the zip, should return an artist', done => { + let updateData = {zip: '99999'}; + request.put(`${url}/api/artist/${this.tempArtist._id}`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) + return done(err); + expect(res.status).to.equal(200); + expect(res.body.firstname).to.equal(this.tempArtist.firstname); + expect(res.body.lastname).to.equal(this.tempArtist.lastname); + expect(res.body.username).to.equal(this.tempArtist.username); + expect(res.body.email).to.equal(this.tempArtist.email); + expect(res.body.city).to.equal(this.tempArtist.city); + expect(res.body.zip).to.equal(updateData.zip); + expect(res.body.about).to.equal(this.tempArtist.about); + expect(res.body.phone).to.equal(this.tempArtist.phone); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + let date = new Date(res.body.created).toString(); + expect(date).to.not.equal('Invalid Date'); + done(); + }); + }); + }); + + describe('with valid token and id', function() { + + before(done => mockArtist.call(this, done)); + + it('updating only the about, should return an artist', done => { + let updateData = {about: 'bob'}; + request.put(`${url}/api/artist/${this.tempArtist._id}`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) + return done(err); + expect(res.status).to.equal(200); + expect(res.body.firstname).to.equal(this.tempArtist.firstname); + expect(res.body.lastname).to.equal(this.tempArtist.lastname); + expect(res.body.username).to.equal(this.tempArtist.username); + expect(res.body.email).to.equal(this.tempArtist.email); + expect(res.body.city).to.equal(this.tempArtist.city); + expect(res.body.zip).to.equal(this.tempArtist.zip); + expect(res.body.about).to.equal(updateData.about); + expect(res.body.phone).to.equal(this.tempArtist.phone); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + let date = new Date(res.body.created).toString(); + expect(date).to.not.equal('Invalid Date'); + done(); + }); + }); + }); + + describe('with valid token and id', function() { + + before(done => mockArtist.call(this, done)); + + it('updating only the phone, should return an artist', done => { + let updateData = {phone: '(555)595-5959'}; + request.put(`${url}/api/artist/${this.tempArtist._id}`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) + return done(err); + expect(res.status).to.equal(200); + expect(res.body.firstname).to.equal(this.tempArtist.firstname); + expect(res.body.lastname).to.equal(this.tempArtist.lastname); + expect(res.body.username).to.equal(this.tempArtist.username); + expect(res.body.email).to.equal(this.tempArtist.email); + expect(res.body.city).to.equal(this.tempArtist.city); + expect(res.body.zip).to.equal(this.tempArtist.zip); + expect(res.body.about).to.equal(this.tempArtist.about); + expect(res.body.phone).to.equal(updateData.phone); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + let date = new Date(res.body.created).toString(); + expect(date).to.not.equal('Invalid Date'); + done(); + }); + }); + }); + + describe('with valid token and id', function() { + + before(done => mockArtist.call(this, done)); + + it('updating only the phone, should return an artist', done => { + let updateData = {phone: 'bob'}; + request.put(`${url}/api/artist/${this.tempArtist._id}`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) + return done(err); + expect(res.status).to.equal(200); + expect(res.body.firstname).to.equal(this.tempArtist.firstname); + expect(res.body.lastname).to.equal(this.tempArtist.lastname); + expect(res.body.username).to.equal(this.tempArtist.username); + expect(res.body.email).to.equal(this.tempArtist.email); + expect(res.body.city).to.equal(this.tempArtist.city); + expect(res.body.zip).to.equal(this.tempArtist.zip); + expect(res.body.about).to.equal(this.tempArtist.about); + expect(res.body.phone).to.equal(updateData.phone); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + let date = new Date(res.body.created).toString(); + expect(date).to.not.equal('Invalid Date'); + done(); + }); + }); + }); + + describe('with valid token and id', function() { + + before(done => mockArtist.call(this, done)); + + it('updating only firstname and lastname, should return an artist', done => { + let updateData = {firstname: 'bob', lastname: 'bob2'}; + request.put(`${url}/api/artist/${this.tempArtist._id}`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) + return done(err); + expect(res.status).to.equal(200); + expect(res.body.firstname).to.equal(updateData.firstname); + expect(res.body.lastname).to.equal(updateData.lastname); + expect(res.body.username).to.equal(this.tempArtist.username); + expect(res.body.email).to.equal(this.tempArtist.email); + expect(res.body.city).to.equal(this.tempArtist.city); + expect(res.body.zip).to.equal(this.tempArtist.zip); + expect(res.body.about).to.equal(this.tempArtist.about); + expect(res.body.phone).to.equal(this.tempArtist.phone); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + let date = new Date(res.body.created).toString(); + expect(date).to.not.equal('Invalid Date'); + done(); + }); + }); + }); + + describe('updating only username and email, should return an artist', function() { + + before(done => mockArtist.call(this, done)); + + it('updating only firstname and lastname, should return an artist', done => { + let updateData = {username: 'bob', email: 'bob2'}; + request.put(`${url}/api/artist/${this.tempArtist._id}`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) + return done(err); + expect(res.status).to.equal(200); + expect(res.body.firstname).to.equal(this.tempArtist.firstname); + expect(res.body.lastname).to.equal(this.tempArtist.lastname); + expect(res.body.username).to.equal(updateData.username); + expect(res.body.email).to.equal(updateData.email); + expect(res.body.city).to.equal(this.tempArtist.city); + expect(res.body.zip).to.equal(this.tempArtist.zip); + expect(res.body.about).to.equal(this.tempArtist.about); + expect(res.body.phone).to.equal(this.tempArtist.phone); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + let date = new Date(res.body.created).toString(); + expect(date).to.not.equal('Invalid Date'); + done(); + }); + }); + }); + + describe('updating only city and zip, should return an artist', function() { + + before(done => mockArtist.call(this, done)); + + it('updating only firstname and lastname, should return an artist', done => { + let updateData = {city: 'bob', zip: 'bob2'}; + request.put(`${url}/api/artist/${this.tempArtist._id}`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) + return done(err); + expect(res.status).to.equal(200); + expect(res.body.firstname).to.equal(this.tempArtist.firstname); + expect(res.body.lastname).to.equal(this.tempArtist.lastname); + expect(res.body.username).to.equal(this.tempArtist.username); + expect(res.body.email).to.equal(this.tempArtist.email); + expect(res.body.city).to.equal(updateData.city); + expect(res.body.zip).to.equal(updateData.zip); + expect(res.body.about).to.equal(this.tempArtist.about); + expect(res.body.phone).to.equal(this.tempArtist.phone); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + let date = new Date(res.body.created).toString(); + expect(date).to.not.equal('Invalid Date'); + done(); + }); + }); + }); + + describe('updating only about and phone, should return an artist', function() { + + before(done => mockArtist.call(this, done)); + + it('updating only firstname and lastname, should return an artist', done => { + let updateData = {about: 'bob', phone: 'bob2'}; + request.put(`${url}/api/artist/${this.tempArtist._id}`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) + return done(err); + expect(res.status).to.equal(200); + expect(res.body.firstname).to.equal(this.tempArtist.firstname); + expect(res.body.lastname).to.equal(this.tempArtist.lastname); + expect(res.body.username).to.equal(this.tempArtist.username); + expect(res.body.email).to.equal(this.tempArtist.email); + expect(res.body.city).to.equal(this.tempArtist.city); + expect(res.body.zip).to.equal(this.tempArtist.zip); + expect(res.body.about).to.equal(updateData.about); + expect(res.body.phone).to.equal(updateData.phone); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + let date = new Date(res.body.created).toString(); + expect(date).to.not.equal('Invalid Date'); + done(); + }); + }); + }); + + describe('with valid token and invalid id', function() { + + before(done => mockArtist.call(this, done)); + + it('should return status 404 not found', done => { + let updateData = {firstName: 'bob'}; + request.put(`${url}/api/artist/${this.tempArtist._id}bad`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(404); + done(); + }); + }); + }); + + describe('updated with empty firstname', function(){ + + before(done => mockArtist.call(this, done)); + + it('should return status 400 bad request', done => { + let updateData = {firstname: ''}; + request.put(`${url}/api/artist/${this.tempArtist._id}`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('updated with empty lastname', function(){ + + before(done => mockArtist.call(this, done)); + + it('should return status 400 bad request', done => { + let updateData = {lastname: ''}; + request.put(`${url}/api/artist/${this.tempArtist._id}`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('updated with empty city', function(){ + + before(done => mockArtist.call(this, done)); + + it('should return status 400 bad request', done => { + let updateData = {city: ''}; + request.put(`${url}/api/artist/${this.tempArtist._id}`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('updated with empty zip', function(){ + + before(done => mockArtist.call(this, done)); + + it('should return status 400 bad request', done => { + let updateData = {zip: ''}; + request.put(`${url}/api/artist/${this.tempArtist._id}`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('with bad token request and valid id', function(){ + + before(done => mockArtist.call(this, done)); + + it('should return status 400 bad request', done => { + let updateData = {firstName: 'bob'}; + request.put(`${url}/api/artist/${this.tempArtist._id}`) + .send(updateData) + .set({ + Authorization: 'Bearer ', + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('with no auth header', function(){ + + before(done => mockArtist.call(this, done)); + + it('should status 400 bad request', done => { + request.put(`${url}/api/artist/${this.tempArtist._id}`) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('with wrong token', function() { + let tempSecondUser = {}; + before(done => mockArtist.call(this, done)); + before(done => mockUser.call(tempSecondUser, done)); + + it('should return status 401 unauthorized', done => { + request.delete(`${url}/api/artist/${this.tempArtist._id}`) + .set({ + Authorization: `Bearer ${this.tempUser.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(401); + done(); + }); + }); + }); + }); + }); + + describe('testing DELETE to /api/artist/:artistID', () => { + + describe('with valid token and id', function() { + + before(done => mockArtist.call(this, done)); + + it('should delete a artist', done => { + request.delete(`${url}/api/artist/${this.tempArtist._id}`) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) + return done(err); + expect(res.status).to.equal(204); + done(); + }); + }); + }); + + describe('with valid token and invalid id', function() { + + before(done => mockArtist.call(this, done)); + + it('should return status 404 not found', done => { + request.delete(`${url}/api/artist/${this.tempArtist._id}bad`) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(404); + done(); + }); + }); + }); + + describe('with bad token request and valid id', function() { + + before(done => mockArtist.call(this, done)); + + it('should return status 400 bad request', done => { + request.delete(`${url}/api/artist/${this.tempArtist._id}`) + .set({ + Authorization: 'Bearer ', + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('with no auth header', function() { + + before(done => mockArtist.call(this, done)); + + it('should return status 400 for bad request', done => { + request.delete(`${url}/api/artist/${this.tempArtist._id}`) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('with wrong token', function() { + let tempSecondUser = {}; + before(done => mockArtist.call(this, done)); + before(done => mockUser.call(tempSecondUser, done)); + + it('should return status 401 unauthorized', done => { + request.delete(`${url}/api/artist/${this.tempArtist._id}`) + .set({ + Authorization: `Bearer ${this.tempUser.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(401); + done(); + }); + }); + }); + + describe('with wrong user and many photos', function() { + let tempSecondUser = {}; + before( done => mockManyPhotos.call(this, 5, done)); + before(done => mockUser.call(tempSecondUser, done)); + + it('should return status 401 unauthorized', done => { + request.delete(`${url}/api/artist/${this.tempArtist._id}`) + .set({ + Authorization: `Bearer ${this.tempUser.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(401); + done(); + }); + }); + }); + + describe('with valid token and id', () => { + + before( done => mockManyPhotos.call(this, 5, done)); + + it('should delete an artist and all associated galleries, listings and photos', done => { + request.delete(`${url}/api/artist/${this.tempArtist._id}`) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) return done(err); + expect(res.status).to.equal(204); + done(); + }); + }); + }); + + describe('with bad token and many photos', () => { + + before( done => mockManyPhotos.call(this, 5, done)); + + it('should delete an artist and all associated galleries, listings and photos', done => { + request.delete(`${url}/api/artist/${this.tempArtist._id}`) + .set({ + Authorization: `Bearer ${this.tempToken}bad`, + }) + .end((err, res) => { + expect(res.status).to.equal(401); + done(); + }); + }); + }); + + describe('with no auth header and many photos', function() { + + before( done => mockManyPhotos.call(this, 5, done)); + + it('should return status 400 for bad request', done => { + request.delete(`${url}/api/artist/${this.tempArtist._id}`) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('with wrong token', function() { + let tempSecondUser = {}; + before(done => mockArtist.call(this, done)); + before(done => mockUser.call(tempSecondUser, done)); + + it('should return status 401 unauthorized', done => { + request.delete(`${url}/api/artist/${this.tempArtist._id}`) + .set({ + Authorization: `Bearer ${this.tempUser.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(401); + done(); + }); + }); + }); + }); +}); diff --git a/lab-claudia/test/auth-router-test.js b/lab-claudia/test/auth-router-test.js new file mode 100644 index 0000000..e8011e4 --- /dev/null +++ b/lab-claudia/test/auth-router-test.js @@ -0,0 +1,486 @@ +'use strict'; + +require('./lib/test-env.js'); +require('./lib/aws-mock.js'); + +// npm modules +const expect = require('chai').expect; +const request = require('superagent'); +const Promise = require('bluebird'); +const mongoose = require('mongoose'); +const validator = require('validator'); + +// app modules +const serverCtrl = require('./lib/server-control'); +const cleanDB = require('./lib/clean-db.js'); +const mockUser = require('./lib/user-mock.js'); +const mockManyPhotos = require('./lib/mock-many-photos.js'); + +mongoose.Promise = Promise; + +// module constants +const server = require('../server.js'); +const url = `http://localhost:${process.env.PORT}`; + +const exampleUser = { + username: 'fakeuser', + password: '192837465', + email: 'fake@fakestuff.fake', +}; + +describe('testing auth-router', function() { + + before( done => serverCtrl.serverUp(server, done)); + + after( done => serverCtrl.serverDown(server, done)); + + afterEach( done => cleanDB(done)); + + describe('testing POST /api/signup', function() { + + describe('with a valid body', function() { + + it('should return a token', (done) => { + + request.post(`${url}/api/signup`) + .send(exampleUser) + .end((err, res) => { + if (err) + return done(err); + expect(res.status).to.equal(200); + expect(!!res.text).to.equal(true); + expect(validator.isEmail(exampleUser.email)).to.equal(true); + done(); + }); + }); + }); + + describe('with no username', function() { + + it('should return a status 400, bad request', (done) => { + + request.post(`${url}/api/signup`) + .send({ + password: exampleUser.password, + email: exampleUser.email, + }) + .end((err, res) => { + expect(res.status).to.equal(400); + expect(res.text).to.equal('BadRequestError'); + done(); + }); + }); + }); + + describe('with no password', function() { + + it('should return a status 400, bad request', (done) => { + + request.post(`${url}/api/signup`) + .send({ + username: exampleUser.username, + email: exampleUser.email, + }) + .end((err, res) => { + expect(res.status).to.equal(400); + expect(res.text).to.equal('BadRequestError'); + done(); + }); + }); + }); + + describe('with no email', function() { + + it('should return a status 400, bad request', (done) => { + + request.post(`${url}/api/signup`) + .send({ + username: exampleUser.username, + password: exampleUser.password, + }) + .end((err, res) => { + expect(res.status).to.equal(400); + expect(res.text).to.equal('BadRequestError'); + done(); + }); + }); + }); + + describe('with duplicate username', function() { + + before( done => mockUser.call(this, done)); + + it('should return a status 409', (done) => { + + request.post(`${url}/api/signup`) + .send({ + username: this.tempUser.username, + password: exampleUser.password, + email: exampleUser.email, + }) + .end((err, res) => { + expect(res.status).to.equal(409); + expect(res.text).to.equal('ConflictError'); + done(); + }); + }); + }); + + describe('with duplicate email', function() { + + before( done => mockUser.call(this, done)); + + it('should return a status 409', (done) => { + + request.post(`${url}/api/signup`) + .send({ + username: exampleUser.username, + password: exampleUser.password, + email: this.tempUser.email, + }) + .end((err, res) => { + expect(res.status).to.equal(409); + expect(res.text).to.equal('ConflictError'); + done(); + }); + }); + }); + + describe('with password < 7 characters', function() { + + it('should return a status 400', (done) => { + + request.post(`${url}/api/signup`) + .send({ + username: exampleUser.username, + password: 'dog', + email: exampleUser.email, + }) + .end((err, res) => { + expect(res.status).to.equal(400); + expect(res.text).to.equal('BadRequestError'); + done(); + }); + }); + }); + }); + + describe('testing GET /api/login', function() { + + describe('with valid authorization', function() { + + before( done => mockUser.call(this, done)); + + it('should return a token', (done) => { + request.get(`${url}/api/login`) + .auth(this.tempUser.username, this.tempPassword) + .end((err, res) => { + if (err) + return done(err); + expect(res.status).to.equal(200); + expect(!!res.text).to.equal(true); + done(); + }); + }); + }); + + describe('with a bad username', function() { + + before( done => mockUser.call(this, done)); + + it('should return a status 401, unauthorized', (done) => { + request.get(`${url}/api/login`) + .auth('notgood', this.tempPassword) + .end((err, res) => { + expect(res.status).to.equal(401); + expect(res.text).to.equal('UnauthorizedError'); + done(); + }); + }); + }); + + describe('with a bad password', function() { + + before( done => mockUser.call(this, done)); + + it('should return a status 401, unauthorized', (done) => { + request.get(`${url}/api/login`) + .auth(this.tempUser.username, 'badpassword') + .end((err, res) => { + expect(res.status).to.equal(401); + expect(res.text).to.equal('UnauthorizedError'); + done(); + }); + }); + }); + }); + + describe('testing PUT /api/user/updateEmail', function() { + + describe('with valid token', function() { + + before( done => mockUser.call(this, done)); + + it('should return a user with a new email', done => { + let updateData = {email: 'bob@bob.bob'}; + request.put(`${url}/api/user/updateEmail`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) + return done(err); + expect(res.status).to.equal(200); + expect(res.body.email).to.equal(updateData.email); + done(); + }); + }); + }); + + describe('with invalid token', function() { + + before( done => mockUser.call(this, done)); + + it('should return a status 400', done => { + let updateData = {email: 'bob@bob.bob'}; + request.put(`${url}/api/user/updateEmail`) + .send(updateData) + .set({ + Authorization: 'Bearer ', + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('with wrong token', function() { + let tempSecondUser = {}; + before( done => mockUser.call(this, done)); + before( done => mockUser.call(tempSecondUser, done)); + + it('should return a user with a new email', done => { + let updateData = {email: 'bob@bob.bob'}; + request.put(`${url}/api/user/updateEmail`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempUser.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(401); + done(); + }); + }); + }); + }); + + describe('testing PUT /api/user/updateUsername', function() { + + describe('with valid token and id', function() { + + before( done => mockUser.call(this, done)); + + it('should return a user with a new username', done => { + let updateData = {username: 'bobbob'}; + request.put(`${url}/api/user/updateUsername`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) + return done(err); + expect(res.status).to.equal(200); + expect(res.body.username).to.equal(updateData.username); + done(); + }); + }); + }); + + describe('with wrong token', function() { + + let tempSecondUser = {}; + before( done => mockUser.call(this, done)); + before( done => mockUser.call(tempSecondUser, done)); + + it('should return a status 404', done => { + let updateData = {username: 'bob'}; + request.put(`${url}/api/user/updateUsername`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempUser.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(401); + done(); + }); + }); + }); + + describe('with invalid token', function() { + + before( done => mockUser.call(this, done)); + + it('should return a status 400', done => { + let updateData = {username: 'bob'}; + request.put(`${url}/api/user/updateUsername`) + .send(updateData) + .set({ + Authorization: 'Bearer ', + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + }); + + describe('testing PUT /api/user/updatePassword', function() { + + describe('with valid token', function() { + + before( done => mockUser.call(this, done)); + + it('should return a user with a new email', done => { + let updateData = {password: 'password'}; + request.put(`${url}/api/user/updatePassword`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) + return done(err); + expect(res.status).to.equal(200); + expect(res.body.password).to.equal(updateData.password); + done(); + }); + }); + }); + + describe('with invalid token', function() { + + before( done => mockUser.call(this, done)); + + it('should return a status 400', done => { + let updateData = {password: 'password'}; + request.put(`${url}/api/user/updatePassword`) + .send(updateData) + .set({ + Authorization: 'Bearer ', + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('with wrong token', function() { + let tempSecondUser = {}; + before( done => mockUser.call(this, done)); + before( done => mockUser.call(tempSecondUser, done)); + + it('should return a user with a new password', done => { + let updateData = {password: 'password'}; + request.put(`${url}/api/user/updatePassword`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempUser.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(401); + done(); + }); + }); + }); + + describe('testing DELETE to /api/artist/:artistID', () => { + + describe('with valid token', () => { + + before( done => mockManyPhotos.call(this, 5, done)); + + it('should delete an artist and all associated galleries, listings and photos', done => { + request.delete(`${url}/api/user/deleteAccount`) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) return done(err); + expect(res.status).to.equal(204); + done(); + }); + }); + }); + + describe('with valid token only ONLY user account w/ no gall, list, etc.', () => { + + before(done => mockUser.call(this, done)); + + it('should delete an artist and all associated galleries, listings and photos', done => { + request.delete(`${url}/api/user/deleteAccount`) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) return done(err); + expect(res.status).to.equal(204); + done(); + }); + }); + }); + + describe('with bad token request.', () => { + + before(done => mockUser.call(this, done)); + + it('should delete an artist and all associated galleries, listings and photos', done => { + request.delete(`${url}/api/user/deleteAccount`) + .set({ + Authorization: 'Bearer ', + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('with wrong token and many photos', function() { + let tempSecondUser = {}; + before( done => mockManyPhotos.call(this, 5, done)); + before(done => mockUser.call(tempSecondUser, done)); + + it('should return status 401 unauthorized', done => { + request.delete(`${url}/api/user/deleteAccount`) + .set({ + Authorization: `Bearer ${this.tempUser.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(401); + done(); + }); + }); + }); + + describe('with bad token and many photos', function() { + before( done => mockManyPhotos.call(this, 5, done)); + + it('should return status 401 unauthorized', done => { + request.delete(`${url}/api/user/deleteAccount`) + .set({ + Authorization: `Bearer ${this.tempUser.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(401); + done(); + }); + }); + }); + }); + }); +}); diff --git a/lab-claudia/test/data/dog.jpg b/lab-claudia/test/data/dog.jpg new file mode 100644 index 0000000..72c9889 Binary files /dev/null and b/lab-claudia/test/data/dog.jpg differ diff --git a/lab-claudia/test/data/doge.png b/lab-claudia/test/data/doge.png new file mode 100644 index 0000000..b861886 Binary files /dev/null and b/lab-claudia/test/data/doge.png differ diff --git a/lab-claudia/test/gallery-router-test.js b/lab-claudia/test/gallery-router-test.js new file mode 100644 index 0000000..01e0164 --- /dev/null +++ b/lab-claudia/test/gallery-router-test.js @@ -0,0 +1,738 @@ +'use strict'; + +// bringing in test environment +require('./lib/test-env.js'); +require('./lib/aws-mock.js'); + +// npm modules +const expect = require('chai').expect; +const request = require('superagent'); +const Promise = require('bluebird'); +const mongoose = require('mongoose'); + +// app modules +const serverCtrl = require('./lib/server-control'); +const cleanDB = require('./lib/clean-db'); +const mockManyPhotos = require('./lib/mock-many-photos'); +const mockArtist = require('./lib/artist-mock'); +const mockGallery = require('./lib/gallery-mock'); +const mockUser = require('./lib/user-mock'); +const mockMultipleListings = require('./lib/populate-gallery-listings-mock.js'); + + +mongoose.Promise = Promise; + +// module constants +const server = require('../server.js'); +const url = `http://localhost:${process.env.PORT}`; + +const exampleGallery = { + name: 'Happy Stuff', + desc: 'this is the best album', + category: 'fun', +}; + +describe('testing gallery-router', function() { + + before( done => serverCtrl.serverUp(server, done)); + + after( done => serverCtrl.serverDown(server, done)); + + afterEach( done => cleanDB(done)); + + describe('testing POST /api/artist/:artistID/gallery', () => { + + describe('with a valid body', () => { + + before(done => mockArtist.call(this, done)); + + it('should return a gallery profile and a status 200', (done) => { + + request.post(`${url}/api/artist/${this.tempArtist._id}/gallery`) + .send(exampleGallery) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) return done(err); + expect(res.status).to.equal(200); + expect(res.body.username).to.equal(this.tempArtist.username); + expect(res.body.name).to.equal(exampleGallery.name); + expect(res.body.desc).to.equal(exampleGallery.desc); + expect(res.body.category).to.equal(exampleGallery.category); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + expect(res.body.artistID).to.equal(this.tempArtist._id.toString()); + let date = new Date(res.body.created).toString(); + expect(date).to.not.equal('Invalid Date'); + done(); + }); + }); + }); + + describe('with a bad user token', () => { + + before(done => mockArtist.call(this, done)); + + it('should return a 401 error unauthorized user', (done) => { + + request.post(`${url}/api/artist/${this.tempArtist._id}/gallery`) + .send(exampleGallery) + .set({ + Authorization: `Bearer ${this.tempToken}bad`, + }) + .end((err, res) => { + expect(res.status).to.equal(401); + done(); + }); + }); + }); + + describe('with bad/invalid artist id', () => { + + before(done => mockArtist.call(this, done)); + + it('should return status 404 for invalid id', (done) => { + + request.post(`${url}/api/artist/${this.tempArtist._id}bad/gallery`) + .send({ + name: exampleGallery.name, + username: this.tempArtist.username, + desc: exampleGallery.desc, + category: exampleGallery.category, + }) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(404); + done(); + }); + }); + }); + + describe('with no name', () => { + + before(done => mockArtist.call(this, done)); + + it('should return status 400 bad request', (done) => { + + request.post(`${url}/api/artist/${this.tempArtist._id}/gallery`) + .send({ + username: this.tempArtist.username, + desc: exampleGallery.desc, + category: exampleGallery.category, + }) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('with no desc', () => { + + before(done => mockArtist.call(this, done)); + + it('should status 400 bad request', (done) => { + + request.post(`${url}/api/artist/${this.tempArtist._id}/gallery`) + .send({ + name: exampleGallery.name, + username: this.tempArtist.username, + category: exampleGallery.category, + }) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('with no category', () => { + + before(done => mockArtist.call(this, done)); + + it('should return an gallery profile and a status 400', (done) => { + + request.post(`${url}/api/artist/${this.tempArtist._id}/gallery`) + .send({ + name: exampleGallery.name, + desc: exampleGallery.desc, + username: this.tempArtist.username, + }) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('with invalid date--string', () => { + + before(done => mockArtist.call(this, done)); + + it('should return an gallery profile and a status 400', (done) => { + + request.post(`${url}/api/artist/${this.tempArtist._id}/gallery`) + .send({ + name: exampleGallery.name, + desc: exampleGallery.desc, + username: exampleGallery.username, + category: exampleGallery.category, + created: 'striiiing', + }) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('with an invalid body', () => { + + before(done => mockArtist.call(this, done)); + + it('should a status 400 bad request', (done) => { + + request.post(`${url}/api/artist/${this.tempArtist._id}/gallery`) + .send('badbody') + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('with a bad authorization header', () => { + + before(done => mockArtist.call(this, done)); + + it('should return status 400 bad request', (done) => { + + request.post(`${url}/api/artist/${this.tempArtist._id}/gallery`) + .send(exampleGallery) + .set({ + Authorization: 'bad request', + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('with no authorization header', () => { + + before(done => mockArtist.call(this, done)); + + it('should return status 400 bad request', (done) => { + + request.post(`${url}/api/artist/${this.tempArtist._id}/gallery`) + .send(exampleGallery) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('with bearer header with no token', () => { + + before(done => mockArtist.call(this, done)); + + it('should return status 400 bad request', (done) => { + + request.post(`${url}/api/artist/${this.tempArtist._id}/gallery`) + .send(exampleGallery) + .set({ + Authorization: 'Bearer ', + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + }); + + describe('testing GET to /api/gallery/:galleryID', () => { + + describe('with valid token and id', () =>{ + + before(done => mockGallery.call(this, done)); + + it('should return a gallery', done => { + request.get(`${url}/api/gallery/${this.tempGallery._id}`) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) return done(err); + expect(res.status).to.equal(200); + expect(res.body.username).to.equal(this.tempGallery.username); + expect(res.body.name).to.equal(this.tempGallery.name); + expect(res.body.desc).to.equal(this.tempGallery.desc); + expect(res.body.category).to.equal(this.tempGallery.category); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + expect(res.body.artistID).to.equal(this.tempArtist._id.toString()); + let date = new Date(res.body.created).toString(); + expect(date).to.not.equal('Invalid Date'); + done(); + }); + }); + }); + + describe('testing populate gallery listings', () => { + + before(done => mockMultipleListings.call(this, 10, done)); + + it('should return a gallery with populated listing array', done => { + request.get(`${url}/api/gallery/${this.tempGallery._id}`) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) return done(err); + expect(res.status).to.equal(200); + expect(res.body.listings.length).to.equal(10); + done(); + }); + }); + }); + + describe('testing populate gallery listings with valid id and bad token request', function(){ + + before(done => mockMultipleListings.call(this, 10, done)); + + it('should return a 400 error bad request', done => { + request.get(`${url}/api/gallery/${this.tempGallery._id}`) + .set({ + Authorization: 'Bearer ', + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('testing populate gallery listings with invalid id and valid token', function(){ + + before(done => mockMultipleListings.call(this, 10, done)); + + it('should return a 404 error for invalid id', done => { + request.get(`${url}/api/gallery/${this.tempGallery._id}bad`) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(404); + done(); + }); + }); + }); + + describe('with valid token and invalid id', () => { + + before(done => mockGallery.call(this, done)); + + it('should return status 404 not found', done => { + request.get(`${url}/api/gallery/${this.tempGallery._id}bad`) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(404); + done(); + }); + }); + }); + + describe('with bad token request and valid id', () => { + + before(done => mockGallery.call(this, done)); + + it('should return status 400 bad request', done => { + request.get(`${url}/api/gallery/${this.tempGallery._id}`) + .set({ + Authorization: 'Bearer ', + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + }); + + describe('testing PUT to /api/gallery/:galleryID', () => { + + describe('updating name property with valid token and id, ', () => { + + before(done => mockGallery.call(this, done)); + + it('should return a gallery', done => { + let updateData = {name: 'bob'}; + request.put(`${url}/api/artist/${this.tempArtist._id}/gallery/${this.tempGallery._id}`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) + return done(err); + expect(res.status).to.equal(200); + expect(res.body.username).to.equal(this.tempGallery.username); + expect(res.body.name).to.equal(updateData.name); + expect(res.body.desc).to.equal(this.tempGallery.desc); + expect(res.body.category).to.equal(this.tempGallery.category); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + expect(res.body.artistID).to.equal(this.tempArtist._id.toString()); + let date = new Date(res.body.created).toString(); + expect(date).to.not.equal('Invalid Date'); + done(); + }); + }); + }); + + describe('updating desc property with valid token and id, ', () => { + + before(done => mockGallery.call(this, done)); + + it('should return a gallery', done => { + let updateData = {desc: 'bob'}; + request.put(`${url}/api/artist/${this.tempArtist._id}/gallery/${this.tempGallery._id}`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) + return done(err); + expect(res.status).to.equal(200); + expect(res.body.username).to.equal(this.tempGallery.username); + expect(res.body.name).to.equal(this.tempGallery.name); + expect(res.body.desc).to.equal(updateData.desc); + expect(res.body.category).to.equal(this.tempGallery.category); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + expect(res.body.artistID).to.equal(this.tempArtist._id.toString()); + let date = new Date(res.body.created).toString(); + expect(date).to.not.equal('Invalid Date'); + done(); + }); + }); + }); + + describe('updating category property with valid token and id, ', () => { + + before(done => mockGallery.call(this, done)); + + it('should return a gallery', done => { + let updateData = {category: 'bob'}; + request.put(`${url}/api/artist/${this.tempArtist._id}/gallery/${this.tempGallery._id}`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) + return done(err); + expect(res.status).to.equal(200); + expect(res.body.username).to.equal(this.tempGallery.username); + expect(res.body.name).to.equal(this.tempGallery.name); + expect(res.body.desc).to.equal(this.tempGallery.desc); + expect(res.body.category).to.equal(updateData.category); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + expect(res.body.artistID).to.equal(this.tempArtist._id.toString()); + let date = new Date(res.body.created).toString(); + expect(date).to.not.equal('Invalid Date'); + done(); + }); + }); + }); + + describe('updating name and category properties with valid token and id, ', () => { + + before(done => mockGallery.call(this, done)); + + it('should return a gallery', done => { + let updateData = {name: 'bob', category: 'bob2'}; + request.put(`${url}/api/artist/${this.tempArtist._id}/gallery/${this.tempGallery._id}`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) + return done(err); + expect(res.status).to.equal(200); + expect(res.body.username).to.equal(this.tempGallery.username); + expect(res.body.name).to.equal(updateData.name); + expect(res.body.desc).to.equal(this.tempGallery.desc); + expect(res.body.category).to.equal(updateData.category); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + expect(res.body.artistID).to.equal(this.tempArtist._id.toString()); + let date = new Date(res.body.created).toString(); + expect(date).to.not.equal('Invalid Date'); + done(); + }); + }); + }); + + describe('updating name and desc properties with valid token and id, ', () => { + + before(done => mockGallery.call(this, done)); + + it('should return a gallery', done => { + let updateData = {name: 'bob', desc: 'bob2'}; + request.put(`${url}/api/artist/${this.tempArtist._id}/gallery/${this.tempGallery._id}`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) + return done(err); + expect(res.status).to.equal(200); + expect(res.body.username).to.equal(this.tempGallery.username); + expect(res.body.name).to.equal(updateData.name); + expect(res.body.desc).to.equal(updateData.desc); + expect(res.body.category).to.equal(this.tempGallery.category); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + expect(res.body.artistID).to.equal(this.tempArtist._id.toString()); + let date = new Date(res.body.created).toString(); + expect(date).to.not.equal('Invalid Date'); + done(); + }); + }); + }); + + describe('updating desc and category properties with valid token and id, ', () => { + + before(done => mockGallery.call(this, done)); + + it('should return a gallery', done => { + let updateData = {desc: 'bob', category: 'bob2'}; + request.put(`${url}/api/artist/${this.tempArtist._id}/gallery/${this.tempGallery._id}`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) + return done(err); + expect(res.status).to.equal(200); + expect(res.body.username).to.equal(this.tempGallery.username); + expect(res.body.name).to.equal(this.tempGallery.name); + expect(res.body.desc).to.equal(updateData.desc); + expect(res.body.category).to.equal(updateData.category); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + expect(res.body.artistID).to.equal(this.tempArtist._id.toString()); + let date = new Date(res.body.created).toString(); + expect(date).to.not.equal('Invalid Date'); + done(); + }); + }); + }); + + describe('updating name, desc and category properties with valid token and id, ', () => { + + before(done => mockGallery.call(this, done)); + + it('should return a gallery', done => { + let updateData = {name: 'bob', desc: 'bob2', category: 'bob3'}; + request.put(`${url}/api/artist/${this.tempArtist._id}/gallery/${this.tempGallery._id}`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) + return done(err); + expect(res.status).to.equal(200); + expect(res.body.username).to.equal(this.tempGallery.username); + expect(res.body.name).to.equal(updateData.name); + expect(res.body.desc).to.equal(updateData.desc); + expect(res.body.category).to.equal(updateData.category); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + expect(res.body.artistID).to.equal(this.tempArtist._id.toString()); + let date = new Date(res.body.created).toString(); + expect(date).to.not.equal('Invalid Date'); + done(); + }); + }); + }); + + describe('with valid token and invalid id', () => { + + before(done => mockGallery.call(this, done)); + + it('should status 404 not found', done => { + let updateData = {name: 'bob'}; + request.put(`${url}/api/artist/${this.tempArtist._id}/gallery/${this.tempGallery._id}bad`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(404); + done(); + }); + }); + }); + + describe('with bad token request and valid id', () => { + + before(done => mockGallery.call(this, done)); + + it('should return status 400 bad request', done => { + let updateData = {name: 'bob'}; + request.put(`${url}/api/artist/${this.tempArtist._id}/gallery/${this.tempGallery._id}`) + .send(updateData) + .set({ + Authorization: 'Bearer ', + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('with wrong user', () => { + + before(done => mockGallery.call(this, done)); + before(done => mockUser.call(this, done)); + + it('should status 401 unauthorized', done => { + request.put(`${url}/api/artist/${this.tempArtist._id}/gallery/${this.tempGallery._id}`) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(401); + done(); + }); + }); + }); + }); + + describe('updated with empty name', () => { + + before(done => mockGallery.call(this, done)); + + it('should status 400 bad request', done => { + let updateData = {name: ''}; + request.put(`${url}/api/artist/${this.tempArtist._id}/gallery/${this.tempGallery._id}`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('testing DELETE to /api/artist/:artistID/gallery/:galleryID', () => { + + describe('with valid token and id', () => { + + before(done => mockGallery.call(this, done)); + it('should delete a gallery', done => { + request.delete(`${url}/api/artist/${this.tempArtist._id}/gallery/${this.tempGallery._id}`) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) + return done(err); + expect(res.status).to.equal(204); + done(); + }); + }); + }); + + describe('with valid token and invalid id', () => { + + before(done => mockGallery.call(this, done)); + + it('should status 404 not found', done => { + request.delete(`${url}/api/artist/${this.tempArtist._id}/gallery/${this.tempGallery._id}bad`) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(404); + done(); + }); + }); + }); + + describe('with bad token request and valid id', () => { + + before(done => mockGallery.call(this, done)); + + it('should status 400 bad request', done => { + request.delete(`${url}/api/artist/${this.tempArtist._id}/gallery/${this.tempGallery._id}`) + .set({ + Authorization: 'Bearer ', + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('with wrong user', () => { + + before(done => mockGallery.call(this, done)); + before(done => mockUser.call(this, done)); + + it('should status 401 unauthorized', done => { + request.delete(`${url}/api/artist/${this.tempArtist._id}/gallery/${this.tempGallery._id}`) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(401); + done(); + }); + }); + }); + + describe('with valid token and id', () => { + + before( done => mockManyPhotos.call(this, 5, done)); + + it('should delete a gallery and all associated listings and photos', done => { + request.delete(`${url}/api/artist/${this.tempArtist._id}/gallery/${this.tempGallery._id}`) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) return done(err); + expect(res.status).to.equal(204); + done(); + }); + }); + }); + }); +}); diff --git a/lab-claudia/test/lib/artist-mock.js b/lab-claudia/test/lib/artist-mock.js new file mode 100644 index 0000000..136225b --- /dev/null +++ b/lab-claudia/test/lib/artist-mock.js @@ -0,0 +1,29 @@ +'use strict'; + +const debug = require('debug')('artc:artist-mock'); +const userMock = require('./user-mock.js'); +const Artist = require('../../model/artist.js'); + +module.exports = function(done){ + debug('create artist mock'); + let exampleArtist = { + firstname: 'george', + lastname: 'bush', + city: 'houston', + zip: '85749', + about: 'former president of the united states of america, avid painter', + phone: '7328851234', + }; + userMock.call(this, err => { + if(err) + return done(err); + exampleArtist.userID = this.tempUser._id.toString(); + exampleArtist.username = this.tempUser.username; + exampleArtist.email = this.tempUser.email; + new Artist(exampleArtist).save() + .then( artist => { + this.tempArtist = artist; + done(); + }); + }); +}; diff --git a/lab-claudia/test/lib/aws-mock.js b/lab-claudia/test/lib/aws-mock.js new file mode 100644 index 0000000..1471ec7 --- /dev/null +++ b/lab-claudia/test/lib/aws-mock.js @@ -0,0 +1,33 @@ +'use strict'; + +const AWSMock = require('aws-sdk-mock'); + +module.exports = exports = {}; + +exports.uploadMock = { + ETag: '"ddad2cbff1bd8023ed7cd42e5059600a"', + Location: 'https://artc-staging-assets.s3.amazonaws.com/03a7990e1adf9fa8944df58dfc81cc5b.jpg', + key: '03a7990e1adf9fa8944df58dfc81cc5b.jpg', + Key: '03a7990e1adf9fa8944df58dfc81cc5b.jpg', + Bucket: 'artc-staging-assets', +}; + +AWSMock.mock('S3', 'upload', function(params, callback){ + if(params.ACL !== 'public-read') + return callback(new Error('ACL must be public read')); + if(params.Bucket !== 'artc-staging-assets') + return callback(new Error('Bucket must be artc-staging-assets')); + if(!params.Key) + return callback(new Error('requres Key')); + if(!params.Body) + return callback(new Error('requires body')); + callback(null, exports.uploadMock); +}); + +AWSMock.mock('S3', 'deleteObject', function(params, callback){ + if(params.Bucket !== 'artc-staging-assets') + return callback(new Error('Bucket must be artc-staging-assets')); + if(!params.Key) + return callback(new Error('requires Key')); + callback(null , {hello: 'sup'}); +}); diff --git a/lab-claudia/test/lib/clean-db.js b/lab-claudia/test/lib/clean-db.js new file mode 100644 index 0000000..6b1f7c5 --- /dev/null +++ b/lab-claudia/test/lib/clean-db.js @@ -0,0 +1,23 @@ +'use strict'; + +const debug = require('debug')('artc:clean-db'); + +const User = require('../../model/user.js'); +const Photo = require('../../model/photo.js'); +const Artist = require('../../model/artist.js'); +const Gallery = require('../../model/gallery.js'); +const Listing = require('../../model/listing.js'); + + +module.exports = function(done){ + debug('clean up database'); + Promise.all([ + Photo.remove({}), + User.remove({}), + Artist.remove({}), + Gallery.remove({}), + Listing.remove({}), + ]) + .then( () => done()) + .catch(done); +}; diff --git a/lab-claudia/test/lib/gallery-mock.js b/lab-claudia/test/lib/gallery-mock.js new file mode 100644 index 0000000..acc64a4 --- /dev/null +++ b/lab-claudia/test/lib/gallery-mock.js @@ -0,0 +1,26 @@ +'use strict'; + +const debug = require('debug')('artc:gallery-mock'); +const artistMock = require('./artist-mock.js'); +const Gallery = require('../../model/gallery.js'); + +module.exports = function(done){ + debug('create mock gallery'); + let exampleGallery = { + name: 'george bush, finest collection', + desc: 'the best portraits george has ever produced', + category: 'portraits', + }; + artistMock.call(this, err => { + if (err) + return done(err); + exampleGallery.username = this.tempUser.username; + exampleGallery.userID = this.tempUser._id.toString(); + exampleGallery.artistID = this.tempArtist._id.toString(); + new Gallery(exampleGallery).save() + .then( gallery => { + this.tempGallery = gallery; + done(); + }); + }); +}; diff --git a/lab-claudia/test/lib/listing-mock.js b/lab-claudia/test/lib/listing-mock.js new file mode 100644 index 0000000..c344093 --- /dev/null +++ b/lab-claudia/test/lib/listing-mock.js @@ -0,0 +1,28 @@ +'use strict'; + +const debug = require('debug')('artc:listing-mock'); +const galleryMock = require('./gallery-mock.js'); +const Listing = require('../../model/listing.js'); + +module.exports = function(done){ + debug('create mock listing'); + let exampleListing = { + title: 'a cat', + desc: 'george does cat portraits', + category: 'portraits', + }; + + galleryMock.call(this, err => { + if (err) + return done(err); + exampleListing.username = this.tempUser.username; + exampleListing.userID = this.tempUser._id.toString(); + exampleListing.artistID = this.tempArtist._id.toString(); + exampleListing.galleryID = this.tempGallery._id.toString(); + new Listing(exampleListing).save() + .then( listing => { + this.tempListing = listing; + done(); + }); + }); +}; diff --git a/lab-claudia/test/lib/mock-many-artists.js b/lab-claudia/test/lib/mock-many-artists.js new file mode 100644 index 0000000..7688b7c --- /dev/null +++ b/lab-claudia/test/lib/mock-many-artists.js @@ -0,0 +1,48 @@ +'use strict'; + +const debug = require('debug')('artc:artist-mock'); +const userMock = require('./user-mock.js'); +const Artist = require('../../model/artist.js'); +const lorem = require('lorem-ipsum'); + +module.exports = function(count, done){ + userMock.call(this, err => { + debug('mock multiple artists'); + if(err) return done(err); + let artistMocks = []; + let userID = this.tempUser._id.toString(); + let email = this.tempUser.email; + let username = this.tempUser.username; + for(var i=0; i { + artists.forEach( artist => { + let artistID = artist._id.toString(); + this.tempUser.artists.push(artistID); + }); + this.tempArtists = artists; + return this.tempArtist.save(); + }) + .then(() => done()) + .catch(done); + }); +}; + +function mockArtist(userID, username, email) { + let firstname = lorem({count:2, units: 'word'}); + let lastname = lorem({count:2, units: 'word'}); + let city = lorem({count:2, units: 'word'}); + let zip = lorem({count:2, units: 'word'}); + let exampleArtist = { + firstname, + lastname, + city, + zip, + userID, + username, + email, + }; + return new Artist(exampleArtist).save(); +} diff --git a/lab-claudia/test/lib/mock-many-photos.js b/lab-claudia/test/lib/mock-many-photos.js new file mode 100644 index 0000000..62aa732 --- /dev/null +++ b/lab-claudia/test/lib/mock-many-photos.js @@ -0,0 +1,48 @@ +'use strict'; + +const debug = require('debug')('artc:mock-many-photos'); +const Photo = require('../../model/photo.js'); +const listingMock = require('./listing-mock.js'); +const lorem = require('lorem-ipsum'); + +module.exports = function(count, done){ + debug('mock multiple photos'); + listingMock.call(this, err => { + if (err) return done(err); + let photoMocks = []; + let userID = this.tempUser._id.toString(); + let artistID = this.tempArtist._id.toString(); + let galleryID = this.tempGallery._id.toString(); + let listingID = this.tempListing._id.toString(); + let username = this.tempUser.username; + for(var i=0; i { + this.tempPhotos = photos; + done(); + }) + .catch(done); + }); +}; + +function mockPic(userID, artistID, galleryID, listingID, username){ + let name = lorem({count:2, units: 'word'}); + let alt = lorem({count:2, units: 'word'}); + let objectKey = lorem({count:4, units: 'word'}).split(' ').join(''); + let uri = lorem({count:4, units: 'word'}).split('').join('-'); + let imageURI = `https://${uri}/${objectKey}`; + let examplePhoto = { + name, + alt, + objectKey, + imageURI, + username, + userID, + artistID, + galleryID, + listingID, + }; + return new Photo(examplePhoto).save(); +} diff --git a/lab-claudia/test/lib/photo-mock.js b/lab-claudia/test/lib/photo-mock.js new file mode 100644 index 0000000..5410cb8 --- /dev/null +++ b/lab-claudia/test/lib/photo-mock.js @@ -0,0 +1,33 @@ +'use strict'; + +const debug = require('debug')('artc:photo-mock'); + +const awsMocks = require('./aws-mock.js'); +const listingMock = require('./listing-mock.js'); + +const Photo = require('../../model/photo.js'); + +module.exports = function(done){ + debug('creating mock photo'); + + let examplePhotoData = { + name: 'example name', + alt: 'useful photo', + imageURI: awsMocks.uploadMock.Location, + objectKey: awsMocks.uploadMock.Key, + }; + + listingMock.call(this, err => { + if (err) return done(err); + examplePhotoData.username = this.tempUser.username; + examplePhotoData.userID = this.tempUser._id.toString(); + examplePhotoData.artistID = this.tempArtist._id.toString(); + examplePhotoData.galleryID = this.tempGallery._id.toString(); + new Photo(examplePhotoData).save() + .then( photo => { + this.tempPhoto = photo; + done(); + }) + .catch(done); + }); +}; diff --git a/lab-claudia/test/lib/populate-artist-galleries-mock.js b/lab-claudia/test/lib/populate-artist-galleries-mock.js new file mode 100644 index 0000000..7cad249 --- /dev/null +++ b/lab-claudia/test/lib/populate-artist-galleries-mock.js @@ -0,0 +1,39 @@ +'use strict'; + +const debug = require('debug')('artc-populate-artist-mocks'); +const artistMock = require('./artist-mock.js'); +const Gallery = require('../../model/gallery.js'); +const lorem = require('lorem-ipsum'); + +module.exports = function(count, done){ + debug('mocking multiple galleries'); + artistMock.call(this, err => { + if (err) return done(err); + let galleriesMock = []; + let userID = this.tempUser._id.toString(); + let artistID = this.tempArtist._id.toString(); + let username = this.tempUser.username; + for(var i=0; i { + galleries.forEach(gallery => { + let galleryID = gallery._id.toString(); + this.tempArtist.galleries.push(galleryID); + }); + this.tempGalleries = galleries; + return this.tempArtist.save(); + }) + .then(() => done()) + .catch(done); + }); +}; + +function mockGallery(userID, artistID, username){ + let name = lorem({count: 2, units: 'word'}); + let desc = lorem({count: 2, units: 'sentence'}); + let category = lorem({count: 2, units: 'word'}); + let exampleGallery = { name, desc, category, userID, artistID, username }; + return new Gallery(exampleGallery).save(); +} diff --git a/lab-claudia/test/lib/populate-gallery-listings-mock.js b/lab-claudia/test/lib/populate-gallery-listings-mock.js new file mode 100644 index 0000000..27201a2 --- /dev/null +++ b/lab-claudia/test/lib/populate-gallery-listings-mock.js @@ -0,0 +1,40 @@ +'use strict'; + +const debug = require('debug')('artc:populate-gallery-listings'); +const galleryMock = require('./gallery-mock.js'); +const Listing = require('../../model/listing.js'); +const lorem = require('lorem-ipsum'); + +module.exports = function(count, done){ + debug('creating multiple listings'); + galleryMock.call(this, err => { + if (err) return done(err); + let listingsMock = []; + let userID = this.tempUser._id.toString(); + let artistID = this.tempArtist._id.toString(); + let galleryID = this.tempGallery._id.toString(); + let username = this.tempUser.username; + for(var i=0; i { + listings.forEach( listing => { + let listingID = listing._id.toString(); + this.tempGallery.listings.push(listingID); + }); + this.tempListings = listings; + return this.tempGallery.save(); + }) + .then(() => done()) + .catch(done); + }); +}; + +function mockListing(userID, artistID, galleryID, username){ + let title = lorem({count: 2, units: 'word'}); + let desc = lorem({count: 2, units: 'sentence'}); + let category = lorem({count: 1, units: 'word'}); + let exampleListing = { title, desc, category, userID, artistID, galleryID, username}; + return new Listing(exampleListing).save(); +} diff --git a/lab-claudia/test/lib/server-control.js b/lab-claudia/test/lib/server-control.js new file mode 100644 index 0000000..0295923 --- /dev/null +++ b/lab-claudia/test/lib/server-control.js @@ -0,0 +1,30 @@ +'use strict'; + +const debug = require('debug')('artc:server-control'); + +module.exports = exports = {}; + +exports.serverUp = function(server, done) { + if (!server.isRunning){ + server.listen(process.env.PORT, () => { + server.isRunning = true; + debug('server up'); + done(); + }); + return; + } + done(); +}; + +exports.serverDown = function(server, done) { + if (server.isRunning) { + server.close(err => { + if (err) return done(err); + server.isRunning = false; + debug('server down'); + done(); + }); + return; + } + done(); +}; diff --git a/lab-claudia/test/lib/test-env.js b/lab-claudia/test/lib/test-env.js new file mode 100644 index 0000000..917f9f3 --- /dev/null +++ b/lab-claudia/test/lib/test-env.js @@ -0,0 +1,8 @@ +'use strict'; + +process.env.MONGODB_URI = 'mongodb://localhost/artctesting'; +process.env.PORT = 3000; +process.env.NODE_ENV = 'testing'; +process.env.APP_SECRET = 'super cool fake secret for tests'; +process.env.AWS_ACCESS_KEY_ID = 'FAKEKEYFORAWS'; +process.env.AWS_SECRET_ACCESS_KEY = 'FAKEACESSKEY'; diff --git a/lab-claudia/test/lib/user-mock.js b/lab-claudia/test/lib/user-mock.js new file mode 100644 index 0000000..b53d45c --- /dev/null +++ b/lab-claudia/test/lib/user-mock.js @@ -0,0 +1,30 @@ +'use strict'; + +const debug = require('debug')('artc:user-mock'); +const User = require('../../model/user.js'); +const lorem = require('lorem-ipsum'); + +module.exports = function(done) { + debug('create mock user'); + let username = lorem({count: 2, units: 'word'}).split(' ').join('-'); + let password = lorem({count: 2, units: 'word'}).split(' ').join('-'); + let email = lorem({count: 2, units: 'word'}).split(' ').join('-'); + let exampleUser = { + username, + password, + email: `${email}@art.fancyartist`, + }; + this.tempPassword = password; + new User(exampleUser) + .generatePasswordHash(exampleUser.password) + .then( user => user.save()) + .then( user => { + this.tempUser = user; + return user.generateToken(); + }) + .then( token => { + this.tempToken = token; + done(); + }) + .catch(done); +}; diff --git a/lab-claudia/test/listing-router-test.js b/lab-claudia/test/listing-router-test.js new file mode 100644 index 0000000..3f199c0 --- /dev/null +++ b/lab-claudia/test/listing-router-test.js @@ -0,0 +1,651 @@ +'use strict'; + +require('./lib/test-env.js'); +require('./lib/aws-mock.js'); + +// npm modules +const expect = require('chai').expect; +const request = require('superagent'); +const Promise = require('bluebird'); +const mongoose = require('mongoose'); + +// app modules +const serverCtrl = require('./lib/server-control.js'); +const cleanDB = require('./lib/clean-db.js'); +const mockGallery = require('./lib/gallery-mock.js'); +const mockListing = require('./lib/listing-mock.js'); +const mockUser = require('./lib/user-mock.js'); +const mockManyPhotos = require('./lib/mock-many-photos.js'); + +mongoose.Promise = Promise; + +// module constants +const server = require('../server.js'); +const url = `http://localhost:${process.env.PORT}`; + +const exampleListing = { + title: 'a cat', + desc: 'george does cat portraits', + category: 'portraits', +}; + +describe('testing listing-router', function(){ + + before(done => serverCtrl.serverUp(server, done)); + after(done => serverCtrl.serverDown(server, done)); + afterEach(done => cleanDB(done)); + + describe('testing POST /api/signup', () => { + + describe('with a valid body', () =>{ + + before(done => mockGallery.call(this, done)); + + it('should return a listing and status 200', done => { + request.post(`${url}/api/gallery/${this.tempGallery._id}/listing`) + .send(exampleListing) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if(err) return done(err); + expect(res.status).to.equal(200); + expect(res.body.title).to.equal(exampleListing.title); + expect(res.body.desc).to.equal(exampleListing.desc); + expect(res.body.category).to.equal(exampleListing.category); + expect(res.body.username).to.equal(this.tempUser.username); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + expect(res.body.artistID).to.equal(this.tempArtist._id.toString()); + expect(res.body.galleryID).to.equal(this.tempGallery._id.toString()); + let date = new Date(res.body.created).toString(); + expect(date).to.not.equal('Invalid Date'); + done(); + }); + }); + }); + + describe('with no title', () => { + + before(done => mockGallery.call(this, done)); + + it('should status 400 bad request', (done) => { + + request.post(`${url}/api/gallery/${this.tempGallery._id}/listing`) + .send({ + username: this.tempGallery.username, + desc: exampleListing.desc, + category: exampleListing.category, + }) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('with no desc', () => { + + before(done => mockGallery.call(this, done)); + + it('should status 400 bad request', (done) => { + + request.post(`${url}/api/gallery/${this.tempGallery._id}/listing`) + .send({ + title: exampleListing.name, + username: this.tempGallery.username, + category: exampleListing.category, + }) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('with no category', () => { + + before(done => mockGallery.call(this, done)); + + it('should return an gallery profile and a status 400', (done) => { + + request.post(`${url}/api/gallery/${this.tempGallery._id}/listing`) + .send({ + title: exampleListing.name, + desc: exampleListing.desc, + username: this.tempGallery.username, + }) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('with invalid date--string', () => { + + before(done => mockGallery.call(this, done)); + + it('should return an gallery profile and a status 400', (done) => { + + request.post(`${url}/api/gallery/${this.tempGallery._id}/listing`) + .send({ + title: exampleListing.name, + desc: exampleListing.desc, + username: exampleListing.username, + category: exampleListing.category, + created: 'striiiing', + }) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('with an invalid body', () => { + + before(done => mockGallery.call(this, done)); + + it('should a status 400 bad request', (done) => { + + request.post(`${url}/api/gallery/${this.tempGallery._id}/listing`) + .send('badbody') + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('with a bad authorization header', () => { + + before(done => mockGallery.call(this, done)); + + it('should return status 400 bad request', (done) => { + + request.post(`${url}/api/gallery/${this.tempGallery._id}/listing`) + .send(exampleListing) + .set({ + Authorization: 'bad request', + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('with no authorization header', () => { + + before(done => mockGallery.call(this, done)); + + it('should status 400 bad request', (done) => { + + request.post(`${url}/api/gallery/${this.tempGallery._id}/listing`) + .send(exampleListing) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('with bearer header with no token', () => { + + before(done => mockGallery.call(this, done)); + + it('should return status 400 bad request', (done) => { + + request.post(`${url}/api/gallery/${this.tempGallery._id}/listing`) + .send(exampleListing) + .set({ + Authorization: 'Bearer ', + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + }); + + describe('testing GET to /api/listing/:listingID', () => { + + describe('with valid token and id', () => { + + before(done => mockListing.call(this, done)); + + it('should return a listing', done => { + request.get(`${url}/api/listing/${this.tempListing._id}`) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) return done(err); + expect(res.status).to.equal(200); + expect(res.body.username).to.equal(this.tempListing.username); + expect(res.body.title).to.equal(this.tempListing.title); + expect(res.body.desc).to.equal(this.tempListing.desc); + expect(res.body.category).to.equal(this.tempListing.category); + expect(res.body.userID).to.equal(this.tempListing.userID.toString()); + expect(res.body.artistID).to.equal(this.tempListing.artistID.toString()); + expect(res.body.galleryID).to.equal(this.tempListing.galleryID.toString()); + let date = new Date(res.body.created).toString(); + expect(date).to.not.equal('Invalid Date'); + done(); + }); + }); + }); + describe('with valid token and invalid id', () => { + + before(done => mockListing.call(this, done)); + + it('should status 404 not found', done => { + request.get(`${url}/api/listing/${this.tempListing._id}bad`) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(404); + done(); + }); + }); + }); + + describe('with bearer header with no token', () => { + + before(done => mockListing.call(this, done)); + + it('should return status 400 bad request', done => { + request.get(`${url}/api/listing/${this.tempListing._id}`) + .set({ + Authorization: 'Bearer ', + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + }); + + describe('testing PUT to /api/listing/:listingID', () => { + + describe('updating title property with valid token and id, ', () => { + + before(done => mockListing.call(this, done)); + + it('should return a listing', done => { + let updateData = {title: 'bob'}; + request.put(`${url}/api/gallery/${this.tempGallery._id}/listing/${this.tempListing._id}`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) + return done(err); + expect(res.status).to.equal(200); + expect(res.body.username).to.equal(this.tempListing.username); + expect(res.body.title).to.equal(updateData.title); + expect(res.body.desc).to.equal(this.tempListing.desc); + expect(res.body.category).to.equal(this.tempListing.category); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + expect(res.body.artistID).to.equal(this.tempArtist._id.toString()); + expect(res.body.galleryID).to.equal(this.tempGallery._id.toString()); + let date = new Date(res.body.created).toString(); + expect(date).to.not.equal('Invalid Date'); + done(); + }); + }); + }); + + describe('updating desc property with valid token and id, ', () => { + + before(done => mockListing.call(this, done)); + + it('should return a listing', done => { + let updateData = {desc: 'bob'}; + request.put(`${url}/api/gallery/${this.tempGallery._id}/listing/${this.tempListing._id}`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) + return done(err); + expect(res.status).to.equal(200); + expect(res.body.username).to.equal(this.tempListing.username); + expect(res.body.title).to.equal(this.tempListing.title); + expect(res.body.desc).to.equal(updateData.desc); + expect(res.body.category).to.equal(this.tempListing.category); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + expect(res.body.artistID).to.equal(this.tempArtist._id.toString()); + expect(res.body.galleryID).to.equal(this.tempGallery._id.toString()); + let date = new Date(res.body.created).toString(); + expect(date).to.not.equal('Invalid Date'); + done(); + }); + }); + }); + + describe('updating category property with valid token and id, ', () => { + + before(done => mockListing.call(this, done)); + + it('should return a listing', done => { + let updateData = {category: 'bob'}; + request.put(`${url}/api/gallery/${this.tempGallery._id}/listing/${this.tempListing._id}`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) + return done(err); + expect(res.status).to.equal(200); + expect(res.body.username).to.equal(this.tempListing.username); + expect(res.body.title).to.equal(this.tempListing.title); + expect(res.body.desc).to.equal(this.tempListing.desc); + expect(res.body.category).to.equal(updateData.category); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + expect(res.body.artistID).to.equal(this.tempArtist._id.toString()); + expect(res.body.galleryID).to.equal(this.tempGallery._id.toString()); + let date = new Date(res.body.created).toString(); + expect(date).to.not.equal('Invalid Date'); + done(); + }); + }); + }); + + describe('updating title and category properties with valid token and id, ', () => { + + before(done => mockListing.call(this, done)); + + it('should return a listing', done => { + let updateData = {title: 'bob', category: 'bob2'}; + request.put(`${url}/api/gallery/${this.tempGallery._id}/listing/${this.tempListing._id}`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) + return done(err); + expect(res.status).to.equal(200); + expect(res.body.username).to.equal(this.tempListing.username); + expect(res.body.title).to.equal(updateData.title); + expect(res.body.desc).to.equal(this.tempListing.desc); + expect(res.body.category).to.equal(updateData.category); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + expect(res.body.artistID).to.equal(this.tempArtist._id.toString()); + expect(res.body.galleryID).to.equal(this.tempGallery._id.toString()); + let date = new Date(res.body.created).toString(); + expect(date).to.not.equal('Invalid Date'); + done(); + }); + }); + }); + + describe('updating title and desc properties with valid token and id, ', () => { + + before(done => mockListing.call(this, done)); + + it('should return a listing', done => { + let updateData = {title: 'bob', desc: 'bob2'}; + request.put(`${url}/api/gallery/${this.tempGallery._id}/listing/${this.tempListing._id}`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) + return done(err); + expect(res.status).to.equal(200); + expect(res.body.username).to.equal(this.tempListing.username); + expect(res.body.title).to.equal(updateData.title); + expect(res.body.desc).to.equal(updateData.desc); + expect(res.body.category).to.equal(this.tempListing.category); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + expect(res.body.artistID).to.equal(this.tempArtist._id.toString()); + expect(res.body.galleryID).to.equal(this.tempGallery._id.toString()); + let date = new Date(res.body.created).toString(); + expect(date).to.not.equal('Invalid Date'); + done(); + }); + }); + }); + + describe('updating desc and category properties with valid token and id, ', () => { + + before(done => mockListing.call(this, done)); + + it('should return a listing', done => { + let updateData = {desc: 'bob', category: 'bob2'}; + request.put(`${url}/api/gallery/${this.tempGallery._id}/listing/${this.tempListing._id}`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) + return done(err); + expect(res.status).to.equal(200); + expect(res.body.username).to.equal(this.tempListing.username); + expect(res.body.title).to.equal(this.tempListing.title); + expect(res.body.desc).to.equal(updateData.desc); + expect(res.body.category).to.equal(updateData.category); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + expect(res.body.artistID).to.equal(this.tempArtist._id.toString()); + expect(res.body.galleryID).to.equal(this.tempGallery._id.toString()); + let date = new Date(res.body.created).toString(); + expect(date).to.not.equal('Invalid Date'); + done(); + }); + }); + }); + + describe('updating title, desc and category properties with valid token and id, ', () => { + + before(done => mockListing.call(this, done)); + + it('should return a listing', done => { + let updateData = {title: 'bob', desc: 'bob2', category: 'bob3'}; + request.put(`${url}/api/gallery/${this.tempGallery._id}/listing/${this.tempListing._id}`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) + return done(err); + expect(res.status).to.equal(200); + expect(res.body.username).to.equal(this.tempGallery.username); + expect(res.body.title).to.equal(updateData.title); + expect(res.body.desc).to.equal(updateData.desc); + expect(res.body.category).to.equal(updateData.category); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + expect(res.body.artistID).to.equal(this.tempArtist._id.toString()); + expect(res.body.galleryID).to.equal(this.tempGallery._id.toString()); + let date = new Date(res.body.created).toString(); + expect(date).to.not.equal('Invalid Date'); + done(); + }); + }); + }); + + describe('with valid token and invalid id', () => { + + before(done => mockListing.call(this, done)); + + it('should status 404 not found', done => { + let updateData = {name: 'bob'}; + request.put(`${url}/api/gallery/${this.tempGallery._id}/listing/${this.tempListing._id}bad`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(404); + done(); + }); + }); + }); + + describe('with bearer header and no token', () => { + + before(done => mockListing.call(this, done)); + + it('should return status 400 bad request', done => { + let updateData = {title: 'bob'}; + request.put(`${url}/api/gallery/${this.tempGallery._id}/listing/${this.tempListing._id}`) + .send(updateData) + .set({ + Authorization: 'Bearer ', + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('with wrong user', () => { + + before(done => mockListing.call(this, done)); + before(done => mockUser.call(this, done)); + + it('should status 401 unauthorized', done => { + request.put(`${url}/api/gallery/${this.tempGallery._id}/listing/${this.tempListing._id}`) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(401); + done(); + }); + }); + }); + + describe('updated with empty name', function(){ + + before(done => mockListing.call(this, done)); + + it('should status 400 bad request', done => { + let updateData = {title: ''}; + request.put(`${url}/api/gallery/${this.tempGallery._id}/listing/${this.tempListing._id}`) + .send(updateData) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + }); + + describe('testing DELETE to /api/listing/:listingID', () => { + + describe('with valid token and id', () => { + + before(done => mockListing.call(this, done)); + + it('should delete a listing', done => { + request.delete(`${url}/api/gallery/${this.tempGallery._id}/listing/${this.tempListing._id}`) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) + return done(err); + expect(res.status).to.equal(204); + done(); + }); + }); + }); + + describe('with valid token and invalid id', () => { + + before(done => mockListing.call(this, done)); + + it('should status 404 not found', done => { + request.delete(`${url}/api/gallery/${this.tempGallery._id}/listing/${this.tempListing._id}bad`) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(404); + done(); + }); + }); + }); + + describe('with bearer header and no token', () => { + + before(done => mockListing.call(this, done)); + + it('should return status 400 bad request', done => { + request.delete(`${url}/api/gallery/${this.tempGallery._id}/listing/${this.tempListing._id}`) + .set({ + Authorization: 'Bearer ', + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('with wrong user', () => { + + before(done => mockListing.call(this, done)); + before(done => mockUser.call(this, done)); + + it('should return status 401 unauthorized', done => { + request.delete(`${url}/api/gallery/${this.tempGallery._id}/listing/${this.tempListing._id}`) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(401); + done(); + }); + }); + }); + + describe('with valid token and id', () => { + + before( done => mockManyPhotos.call(this, 5, done)); + + it('should delete a listing and all associated photos and references', done => { + request.delete(`${url}/api/gallery/${this.tempGallery._id}/listing/${this.tempListing._id}`) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if (err) return done(err); + expect(res.status).to.equal(204); + done(); + }); + }); + }); + }); +}); diff --git a/lab-claudia/test/page-router-test.js b/lab-claudia/test/page-router-test.js new file mode 100644 index 0000000..a7a1b9b --- /dev/null +++ b/lab-claudia/test/page-router-test.js @@ -0,0 +1,112 @@ +'use strict'; + +// testing for page queries on all relevant models + +require('./lib/test-env.js'); +require('./lib/aws-mock.js'); + +// npm modules +const expect = require('chai').expect; +const request = require('superagent'); +const mongoose = require('mongoose'); +const Promise = require('bluebird'); + +// app modules +const serverCtrl = require('./lib/server-control'); +const cleanDB = require('./lib/clean-db'); +//const mockUser = require('./lib/user-mock'); +//const mockArtist = require('./lib/artist-mock'); +const mockManyGalleries = require('./lib/populate-artist-galleries-mock.js'); +const mockManyListings = require('./lib/populate-gallery-listings-mock.js'); + +mongoose.Promise = Promise; + +// app constants +const server = require('../server.js'); +const url = `http://localhost:${process.env.PORT}`; + +// const exampleGallery = { +// name: 'Happy Stuff', +// desc: 'this is the best album', +// category: 'fun', +// }; + +//TESTS NEEDED + +describe('testing page-router', function(){ + //start/stop server and clean database + before(done => serverCtrl.serverUp(server, done)); + after(done => serverCtrl.serverDown(server, done)); + afterEach(done => cleanDB(done)); + + + describe('testing /api/listing', function() { + describe('with pagenation' , function() { + before(done => mockManyListings.call(this, 100, done)); + it('should return 50 listings', done => { + request.get(`${url}/api/listing`) + .end((err, res) => { + if (err) return done(err); + expect(res.status).to.equal(200); + expect(res.body.length).to.equal(50); + done(); + }); + }); + }); + + describe('with /api/listing?page=5' , function() { + before(done => mockManyListings.call(this, 100, done)); + it('should return 50 listings', done => { + request.get(`${url}/api/listing?page=5`) + .end((err, res) => { + if (err) return done(err); + for (let i=0; i< res.body.length; i++){ + expect(res.body.listings[i]._id.toString()).to.equal(this.tempListings[i + 10 ]._id.toString()); + expect(res.body.listings[i].artistID.toString()).to.equal(this.tempListings[i + 10 ].artistID.toString()); + expect(res.body.listings[i].galleryID.toString()).to.equal(this.tempListings[i + 10 ].galleryID.toString()); + expect(res.body.listings[i].category).to.equal(this.tempListings[i + 10].category); + expect(res.body.listings[i].name).to.equal(this.tempListings[i + 10].name); + expect(res.body.listings[i].desc).to.equal(this.tempListings[i + 10].desc); + } + expect(res.status).to.equal(200); + done(); + }); + }); + }); + }); //end testing listing pagenation + + describe('testing /api/gallery', function() { + describe('with pagenation' , function() { + before(done => mockManyGalleries.call(this, 100, done)); + it('should return 50 galleries', done => { + request.get(`${url}/api/gallery`) + .end((err, res) => { + if (err) return done(err); + expect(res.status).to.equal(200); + expect(res.body.length).to.equal(50); + done(); + }); + }); + }); + + describe('with /api/gallery?page=5' , function() { + before(done => mockManyGalleries.call(this, 100, done)); + it('should return 50 listings', done => { + request.get(`${url}/api/gallery?page=5`) + .end((err, res) => { + if (err) return done(err); + for (let i=0; i< res.body.length; i++){ + expect(res.body.galleries[i]._id.toString()).to.equal(this.tempGalleries[i + 10 ]._id.toString()); + expect(res.body.galleries[i].artistID.toString()).to.equal(this.tempGalleries[i + 10 ].artistID.toString()); + expect(res.body.galleries[i].category).to.equal(this.tempGalleries[i + 10].category); + expect(res.body.galleries[i].desc).to.equal(this.tempGalleries[i + 10].desc); + } + expect(res.status).to.equal(200); + done(); + }); + }); + }); + + + }); // end testing /api/gallery +}); // end first describe block diff --git a/lab-claudia/test/photo-router-test.js b/lab-claudia/test/photo-router-test.js new file mode 100644 index 0000000..9abe280 --- /dev/null +++ b/lab-claudia/test/photo-router-test.js @@ -0,0 +1,474 @@ +'use strict'; + +require('./lib/test-env.js'); +const awsMocks = require('./lib/aws-mock.js'); + +// NPM MODULES +const expect = require('chai').expect; +const request = require('superagent'); + +// APP MODULES +const artistMock = require('./lib/artist-mock.js'); +const photoMock = require('./lib/photo-mock.js'); +const galleryMock = require('./lib/gallery-mock.js'); +const listingMock = require('./lib/listing-mock.js'); +const cleanDB = require('./lib/clean-db.js'); +const serverControl = require('./lib/server-control.js'); + +// APP MODULES +const server = require('../server.js'); +const url = `http://localhost:${process.env.PORT}`; + +const examplePhoto = { + name: 'goose', + alt: 'good times', + image: `${__dirname}/data/dog.jpg`, +}; + +describe('testing photo router', function() { + + before(done => serverControl.serverUp(server, done)); + after(done => serverControl.serverDown(server, done)); + afterEach(done => cleanDB(done)); + + describe('testing POST routes - /api/artist/:artistID/photo', function() { + describe('with valid token and data', function() { + + before(done => artistMock.call(this, done)); + + it ('should return a photo', done => { + request.post(`${url}/api/artist/${this.tempArtist._id}/photo`) + .set({Authorization: `Bearer ${this.tempToken}`}) + .field('name', examplePhoto.name) + .field('alt', examplePhoto.alt) + .attach('image', examplePhoto.image) + .end((err, res) => { + if (err) return done(err); + expect(res.status).to.equal(200); + expect(res.body.name).to.equal(examplePhoto.name); + expect(res.body.alt).to.equal(examplePhoto.alt); + expect(res.body.imageURI).to.equal(awsMocks.uploadMock.Location); + expect(res.body.objectKey).to.equal(awsMocks.uploadMock.Key); + done(); + }); + }); + }); + + describe('with no image', function() { + + before(done => artistMock.call(this, done)); + + it('should respond with status 400', done => { + request.post(`${url}/api/artist/${this.tempArtist._id}/photo`) + .set({Authorization: `Bearer ${this.tempToken}`}) + .field('name', examplePhoto.name) + .field('alt', examplePhoto.alt) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('with an invalid token', function() { + + before(done => artistMock.call(this, done)); + + it('should respond with status 401', done => { + request.post(`${url}/api/artist/${this.tempArtist._id}/photo`) + .set({Authorization:`Bearer ${this.tempToken}12345`}) + .field('name', examplePhoto.name) + .field('alt', examplePhoto.alt) + .attach('image', examplePhoto.image) + .end((err, res) => { + expect(res.status).to.equal(401); + done(); + }); + }); + }); + + describe('with an invalid artistID', function() { + + before(done => artistMock.call(this, done)); + + it('should respond with status 404', done => { + request.post(`${url}/api/artist/${this.tempArtist._id}goose/photo`) + .set({Authorization: `Bearer ${this.tempToken}`}) + .field('name', examplePhoto.name) + .field('alt', examplePhoto.alt) + .attach('image', examplePhoto.image) + .end((err, res) => { + expect(res.status).to.equal(404); + done(); + }); + }); + }); + }); + + describe('testing DELETE routes - /api/artist/:artistID/photo/:photoID', function() { + describe('with valid token and data', function() { + + before(done => photoMock.call(this, done)); + + it ('should return a photo', done => { + request.delete(`${url}/api/artist/${this.tempArtist._id}/photo/${this.tempPhoto._id}`) + .set({Authorization: `Bearer ${this.tempToken}`}) + .end((err, res) => { + if (err) return done(err); + expect(res.status).to.equal(204); + done(); + }); + }); + }); + + describe('with invalid token/no bearer auth', function() { + + before(done => photoMock.call(this, done)); + + it ('should respond with 401 UnauthorizedError', done => { + request.delete(`${url}/api/artist/${this.tempArtist._id}/photo/${this.tempPhoto._id}`) + .set({Authorization: 'Bearer bad'}) + .end((err, res) => { + expect(res.status).to.equal(401); + done(); + }); + }); + }); + + describe('no auth header', function(){ + + before(done => photoMock.call(this, done)); + + it('should respond with status 400 bad request', done => { + request.delete(`${url}/api/artist/${this.tempArtist._id}/photo/${this.tempPhoto._id}`) + .end((err, res) => { + expect(res.status).to.equal(400); + expect(res.text).to.equal('BadRequestError'); + done(); + }); + }); + }); + + describe('with invalid artistID', function() { + + before(done => photoMock.call(this, done)); + + it ('should return id not found', done => { + request.delete(`${url}/api/artist/${this.tempArtist._id}bad/photo/${this.tempPhoto._id}`) + .set({Authorization: `Bearer ${this.tempToken}`}) + .end((err, res) => { + expect(res.status).to.equal(404); + done(); + }); + }); + }); + + describe('with invalid photoID', function() { + + before(done => photoMock.call(this, done)); + + it ('should return id not found', done => { + request.delete(`${url}/api/artist/${this.tempArtist._id}/photo/${this.tempPhoto._id}bad`) + .set({Authorization: `Bearer ${this.tempToken}`}) + .end((err, res) => { + expect(res.status).to.equal(404); + done(); + }); + }); + }); + }); + + describe('testing POST /api/gallery/:galleryID/photo', function() { + describe('with valid token and data', function() { + + before(done => galleryMock.call(this, done)); + + it ('should return a photo', done => { + request.post(`${url}/api/gallery/${this.tempGallery._id}/photo`) + .set({Authorization: `Bearer ${this.tempToken}`}) + .field('name', examplePhoto.name) + .field('alt', examplePhoto.alt) + .attach('image', examplePhoto.image) + .end((err, res) => { + if (err) return done(err); + expect(res.status).to.equal(200); + expect(res.body.name).to.equal(examplePhoto.name); + expect(res.body.alt).to.equal(examplePhoto.alt); + expect(res.body.imageURI).to.equal(awsMocks.uploadMock.Location); + expect(res.body.objectKey).to.equal(awsMocks.uploadMock.Key); + done(); + }); + }); + }); + + describe('with no image', function() { + + before(done => galleryMock.call(this, done)); + + it('should respond with status 400', done => { + request.post(`${url}/api/gallery/${this.tempGallery._id}/photo`) + .set({Authorization: `Bearer ${this.tempToken}`}) + .field('name', examplePhoto.name) + .field('alt', examplePhoto.alt) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('with an invalid token', function() { + + before(done => galleryMock.call(this, done)); + + it('should respond with status 401', done => { + request.post(`${url}/api/gallery/${this.tempGallery._id}/photo`) + .set({Authorization:`Bearer ${this.tempToken}12345`}) + .field('name', examplePhoto.name) + .field('alt', examplePhoto.alt) + .attach('image', examplePhoto.image) + .end((err, res) => { + expect(res.status).to.equal(401); + done(); + }); + }); + }); + + describe('with an invalid galleryID', function() { + + before(done => galleryMock.call(this, done)); + + it('should respond with status 404', done => { + request.post(`${url}/api/artist/${this.tempGallery._id}goose/photo`) + .set({Authorization: `Bearer ${this.tempToken}`}) + .field('name', examplePhoto.name) + .field('alt', examplePhoto.alt) + .attach('image', examplePhoto.image) + .end((err, res) => { + expect(res.status).to.equal(404); + done(); + }); + }); + }); + }); + + describe('testing DELETE routes - /api/gallery/:galleryID/photo/:photoID', function() { + describe('with valid token and data', function() { + + before(done => photoMock.call(this, done)); + + it ('should return a photo', done => { + request.delete(`${url}/api/gallery/${this.tempGallery._id}/photo/${this.tempPhoto._id}`) + .set({Authorization: `Bearer ${this.tempToken}`}) + .end((err, res) => { + if (err) return done(err); + expect(res.status).to.equal(204); + done(); + }); + }); + }); + + describe('with invalid token/no bearer auth', function() { + + before(done => photoMock.call(this, done)); + + it ('should respond with 401 UnauthorizedError', done => { + request.delete(`${url}/api/gallery/${this.tempGallery._id}/photo/${this.tempPhoto._id}`) + .set({Authorization: 'Bearer bad'}) + .end((err, res) => { + expect(res.status).to.equal(401); + done(); + }); + }); + }); + + describe('with no auth header', function(){ + + before(done => photoMock.call(this, done)); + + it('should respond with status 400 bad request', done => { + request.delete(`${url}/api/gallery/${this.tempGallery._id}/photo/${this.tempPhoto._id}`) + .end((err, res) => { + expect(res.status).to.equal(400); + expect(res.text).to.equal('BadRequestError'); + done(); + }); + }); + }); + + describe('with invalid artistID', function() { + + before(done => photoMock.call(this, done)); + + it ('should return id not found', done => { + request.delete(`${url}/api/gallery/${this.tempGallery._id}bad/photo/${this.tempPhoto._id}`) + .set({Authorization: `Bearer ${this.tempToken}`}) + .end((err, res) => { + expect(res.status).to.equal(404); + done(); + }); + }); + }); + + describe('with invalid photoID', function() { + + before(done => photoMock.call(this, done)); + + it ('should return id not found', done => { + request.delete(`${url}/api/gallery/${this.tempGallery._id}/photo/${this.tempPhoto._id}bad`) + .set({Authorization: `Bearer ${this.tempToken}`}) + .end((err, res) => { + expect(res.status).to.equal(404); + done(); + }); + }); + }); + }); + + describe('testing POST /api/listing/:listingID/photo', function() { + describe('with valid token and data', function() { + + before(done => listingMock.call(this, done)); + + it ('should return a photo', done => { + request.post(`${url}/api/listing/${this.tempListing._id}/photo`) + .set({Authorization: `Bearer ${this.tempToken}`}) + .field('name', examplePhoto.name) + .field('alt', examplePhoto.alt) + .attach('image', examplePhoto.image) + .end((err, res) => { + if (err) return done(err); + expect(res.status).to.equal(200); + expect(res.body.name).to.equal(examplePhoto.name); + expect(res.body.alt).to.equal(examplePhoto.alt); + expect(res.body.imageURI).to.equal(awsMocks.uploadMock.Location); + expect(res.body.objectKey).to.equal(awsMocks.uploadMock.Key); + done(); + }); + }); + }); + + describe('with no image', function() { + + before(done => listingMock.call(this, done)); + + it('should respond with status 400', done => { + request.post(`${url}/api/listing/${this.tempListing._id}/photo`) + .set({Authorization: `Bearer ${this.tempToken}`}) + .field('name', examplePhoto.name) + .field('alt', examplePhoto.alt) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('with an invalid token', function() { + + before(done => listingMock.call(this, done)); + + it('should respond with status 401', done => { + request.post(`${url}/api/listing/${this.tempListing._id}/photo`) + .set({Authorization:`Bearer ${this.tempToken}12345`}) + .field('name', examplePhoto.name) + .field('alt', examplePhoto.alt) + .attach('image', examplePhoto.image) + .end((err, res) => { + expect(res.status).to.equal(401); + done(); + }); + }); + }); + + describe('with an invalid galleryID', function() { + + before(done => listingMock.call(this, done)); + + it('should respond with status 404', done => { + request.post(`${url}/api/listing/${this.tempListing._id}goose/photo`) + .set({Authorization: `Bearer ${this.tempToken}`}) + .field('name', examplePhoto.name) + .field('alt', examplePhoto.alt) + .attach('image', examplePhoto.image) + .end((err, res) => { + expect(res.status).to.equal(404); + done(); + }); + }); + }); + }); + + describe('testing DELETE /api/gallery/:galleryID/photo/:photoID', function(){ + describe('with valid token and data', function() { + + before(done => photoMock.call(this, done)); + + it ('should return a photo', done => { + request.delete(`${url}/api/listing/${this.tempListing._id}/photo/${this.tempPhoto._id}`) + .set({Authorization: `Bearer ${this.tempToken}`}) + .end((err, res) => { + if (err) return done(err); + expect(res.status).to.equal(204); + done(); + }); + }); + }); + + describe('with invalid token/no bearer auth', function() { + + before(done => photoMock.call(this, done)); + + it ('should respond with 401 UnauthorizedError', done => { + request.delete(`${url}/api/listing/${this.tempListing._id}/photo/${this.tempPhoto._id}`) + .set({Authorization: 'Bearer bad'}) + .end((err, res) => { + expect(res.status).to.equal(401); + done(); + }); + }); + }); + + describe('with no auth header', function(){ + + before(done => photoMock.call(this, done)); + + it('should respond with status 400 bad request', done => { + request.delete(`${url}/api/listing/${this.tempListing._id}/photo/${this.tempPhoto._id}`) + .end((err, res) => { + expect(res.status).to.equal(400); + expect(res.text).to.equal('BadRequestError'); + done(); + }); + }); + }); + + describe('with invalid artistID', function() { + + before(done => photoMock.call(this, done)); + + it ('should return id not found', done => { + request.delete(`${url}/api/listing/${this.tempListing._id}bad/photo/${this.tempPhoto._id}`) + .set({Authorization: `Bearer ${this.tempToken}`}) + .end((err, res) => { + expect(res.status).to.equal(404); + done(); + }); + }); + }); + + describe('with invalid photoID', function() { + + before(done => photoMock.call(this, done)); + + it ('should return id not found', done => { + request.delete(`${url}/api/listing/${this.tempListing._id}/photo/${this.tempPhoto._id}bad`) + .set({Authorization: `Bearer ${this.tempToken}`}) + .end((err, res) => { + expect(res.status).to.equal(404); + done(); + }); + }); + }); + }); +}); diff --git a/lab-claudia/webpack.config.js b/lab-claudia/webpack.config.js new file mode 100644 index 0000000..000c6b3 --- /dev/null +++ b/lab-claudia/webpack.config.js @@ -0,0 +1,75 @@ +'use strict'; + +require('dotenv').load({path: `${__dirname}/.client.env`}); +if (!process.env.API_URL || !process.env.NODE_ENV || !process.env.TITLE){ + console.error('ERROR: ng-template requires .env file'); + process.exit(1); +} + +const webpack = require('webpack'); +const HTMLPlugin = require('html-webpack-plugin'); +const CleanPlugin = require('clean-webpack-plugin'); +const ExtractTextPlugin = require('extract-text-webpack-plugin'); + +const production = process.env.NODE_ENV === 'production'; + +let plugins = [ + new ExtractTextPlugin('bundle.css'), + new HTMLPlugin({template: `${__dirname}/app/index.html`}), + new webpack.DefinePlugin({ + __API_URL__: JSON.stringify(process.env.API_URL), + __GOOGLE_CLIENT_ID__: JSON.stringify(process.env.GOOGLE_CLIENT_ID), + __TITLE__: JSON.stringify(process.env.TITLE), + __DEBUG__: JSON.stringify(!production), + }), +]; + +if (production){ + plugins = plugins.concat([ + new webpack.optimize.UglifyJsPlugin({ + mangle: true, + compress: { + warnings: false, + }, + }), + new CleanPlugin(), + ]); +} + +module.exports = { + entry: `${__dirname}/app/entry.js`, + devtool: production ? false : 'eval', + plugins, + output: { + path: 'build', + filename: 'bundle.js', + }, + sassLoader: { + includePaths: [`${__dirname}/app/scss/lib`], + }, + module: { + loaders: [ + { + test: /\.js$/, + exclude: /node_modules/, + loader: 'babel', + }, + { + test: /\.html$/, + loader: 'html', + }, + { + test: /\.(woff|ttf|svg|eot).*/, + loader: 'url?limit=10000&name=font/[name].[ext]', + }, + { + test: /\.(jpg|jpeg|bmp|tiff|gif|png)$/, + loader: 'url?limit=10000&name=image/[hash].[ext]', + }, + { + test: /\.scss$/, + loader: ExtractTextPlugin.extract('style', 'css!resolve-url!sass?sourceMap'), + }, + ], + }, +};