diff --git a/.gitignore b/.gitignore deleted file mode 100644 index ddd3836..0000000 --- a/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -**/*.sw? -**/node_modules -**/db diff --git a/kenji_crosland/.gitignore b/kenji_crosland/.gitignore new file mode 100644 index 0000000..114214c --- /dev/null +++ b/kenji_crosland/.gitignore @@ -0,0 +1,5 @@ +**/*.sw? +node_modules +db +build +**/*bundle.js diff --git a/kenji_crosland/app/index.html b/kenji_crosland/app/index.html new file mode 100644 index 0000000..7c44701 --- /dev/null +++ b/kenji_crosland/app/index.html @@ -0,0 +1,66 @@ + + + + + + + Our Super Recipe App + + +
+
+

Our Super Recipe App

+
+
+
+ + + +
+
+
+
+
    +
  • + +
  • +
+
+
+
+
+
+ Ingredients: +
    +
  • + {{ingredient}} +
  • +
+ + +
+
+
+
+ + + diff --git a/kenji_crosland/app/js/entry.js b/kenji_crosland/app/js/entry.js new file mode 100644 index 0000000..6c9d721 --- /dev/null +++ b/kenji_crosland/app/js/entry.js @@ -0,0 +1,6 @@ +require('angular/angular'); +var angular = window.angular; + +var recipeApp = angular.module('recipeApp', []); +require('./services/services')(recipeApp); +require('./recipes/recipes')(recipeApp); diff --git a/kenji_crosland/app/js/recipes/controllers/recipes_controller.js b/kenji_crosland/app/js/recipes/controllers/recipes_controller.js new file mode 100644 index 0000000..7709fde --- /dev/null +++ b/kenji_crosland/app/js/recipes/controllers/recipes_controller.js @@ -0,0 +1,64 @@ +module.exports = function(app) { + app.controller('RecipesController', ['$scope', '$http', 'cfResource', function($scope, $http, cfResource){ + $scope.recipes = []; + $scope.editing = {}; + $scope.currentRecipe = null; + + $scope.seeRecipe = function(recipe){ + $scope.currentRecipe = recipe; + } + + $scope.makeCopy = function(recipe) { + recipe.editing = true; + $scope.editing[recipe._id] = angular.copy(recipe); + } + + $scope.cancelEditing = function(recipe) { + $scope.editing[recipe._id].editing = false; + for(i = 0; i < $scope.recipes.length; i++){ + if($scope.recipes[i]._id === recipe._id){ + angular.copy($scope.editing[recipe._id], $scope.recipes[i]); + delete $scope.editing[recipe._id]; + return; + } + } + } + + $scope.addIngredientField = function(recipe) { + if (recipe.ingredients[recipe.ingredients.length - 1] !== "") { + recipe.ingredients.push(""); + } + } + + $scope.removeIngredientField = function(recipe, ingredient) { + recipe.ingredients.splice(recipe.ingredients.indexOf(ingredient), 1); + } + + $scope.getAll = function() { + cfResource('/allrecipes').getAll(function(err, data){ + if (err) return err; + $scope.recipes = data; + }); + }; + + $scope.create = function(recipe) { + cfResource('/recipes', recipe).post(function(err, data){ + $scope.recipes.push(data); + }); + }; + + $scope.update = function(recipe) { + recipe.editing = false; + cfResource('/recipes/' + recipe._id, recipe).put(function(err, data) { + console.log('Recipe updated!'); + }); + } + + $scope.remove = function(recipe) { + $scope.recipes.splice($scope.recipes.indexOf(recipe), 1); + cfResource('/recipes/' + recipe._id).delete(function(err, data){ + console.log('Totes cool. Recipe is gone.') + }) + } + }]); +} diff --git a/kenji_crosland/app/js/recipes/directives/card_directive.js b/kenji_crosland/app/js/recipes/directives/card_directive.js new file mode 100644 index 0000000..a2f9b53 --- /dev/null +++ b/kenji_crosland/app/js/recipes/directives/card_directive.js @@ -0,0 +1,15 @@ +module.exports = function(app) { + app.directive('cardDirective', function() { + return { + restrict: 'AC', + replace: true, + transclude: true, + templateUrl: '/templates/card_template.html', + scope: { + header: '@', + headerText: '@', + width: '@' //two columns, three columns, etc + } + } + }) +} diff --git a/kenji_crosland/app/js/recipes/directives/recipe_form_directive.js b/kenji_crosland/app/js/recipes/directives/recipe_form_directive.js new file mode 100644 index 0000000..f8e99c4 --- /dev/null +++ b/kenji_crosland/app/js/recipes/directives/recipe_form_directive.js @@ -0,0 +1,19 @@ +module.exports = function(app) { + app.directive('recipeFormDirective', function() { + return { + restrict: 'AC', + replace: true, + templateUrl: '/templates/recipe_form_template.html', + scope: { + buttonText: '@', + formClass: '@', + formName: '@', + recipe: '=', + addIngredientField: '&', + removeIngredientField: '&', + cancel: '&', + save: '&' + } + } + }) +} diff --git a/kenji_crosland/app/js/recipes/recipes.js b/kenji_crosland/app/js/recipes/recipes.js new file mode 100644 index 0000000..b2e2a10 --- /dev/null +++ b/kenji_crosland/app/js/recipes/recipes.js @@ -0,0 +1,5 @@ +module.exports = function(app){ + require('./controllers/recipes_controller')(app); + require('./directives/card_directive')(app); + require('./directives/recipe_form_directive')(app); +}; diff --git a/kenji_crosland/app/js/services/cf_resource.js b/kenji_crosland/app/js/services/cf_resource.js new file mode 100644 index 0000000..12c9ba7 --- /dev/null +++ b/kenji_crosland/app/js/services/cf_resource.js @@ -0,0 +1,43 @@ +//Made with help from watching in-class code videos + +var handleSuccess = function(callback) { + return function(res) { + callback(null, res.data) + }; +}; + +var handleFail = function(callback) { + return function(res) { + console.log(res); + callback(res.data); + } +} + +module.exports = function(app) { + app.factory('cfResource', ['$http', function($http) { + return function(resourceName, resourceData) { + var resource = {}; + resource.getAll = function(callback) { + $http.get(resourceName) + .then(handleSuccess(callback), handleFail(callback)); + }; + resource.get = function(callback) { + $http.get(resourceName) + .then(handleSuccess(callback), handleFail(callback)); + }; + resource.post = function(callback) { + $http.post(resourceName, resourceData) + .then(handleSuccess(callback), handleFail(callback)); + }; + resource.put = function(callback) { + $http.put(resourceName, resourceData) + .then(handleSuccess(callback), handleFail(callback)); + } + resource.delete = function(callback) { + $http.delete(resourceName) + .then(handleSuccess(callback), handleFail(callback)); + } + return resource; + }; + }]); +}; diff --git a/kenji_crosland/app/js/services/services.js b/kenji_crosland/app/js/services/services.js new file mode 100644 index 0000000..d43501e --- /dev/null +++ b/kenji_crosland/app/js/services/services.js @@ -0,0 +1,3 @@ +module.exports = function(app) { + require('./cf_resource')(app); +}; diff --git a/kenji_crosland/app/sass/_base.scss b/kenji_crosland/app/sass/_base.scss new file mode 100644 index 0000000..007fed4 --- /dev/null +++ b/kenji_crosland/app/sass/_base.scss @@ -0,0 +1,41 @@ +@import url(https://fonts.googleapis.com/css?family=Nunito); +@import url(https://fonts.googleapis.com/css?family=Dancing+Script); +@import url(https://fonts.googleapis.com/css?family=Ledger); + +html {font-size: 1.125em;} + +body { + font-family: 'Ledger', serif; + background-color: $light-background; + font-weight: 400; + line-height: 1.45; + color: #333; +} + +p {margin-bottom: 1.3em;} + +h1, h2, h3, h4 { + font-family: 'Ledger', serif; + margin: 1.414em 0 0.5em; + font-weight: inherit; + line-height: 1.2; +} + +h1 { + margin-top: 0; + font-size: 2.074em; +} + +h2 {font-size: 1.728em;} + +h3 {font-size: 1.44em;} + +h4 {font-size: 1.2em;} + +small, .font_small {font-size: 0.833em;} + +//Forms + +input { + display: table-cell; +} diff --git a/kenji_crosland/app/sass/_reset.scss b/kenji_crosland/app/sass/_reset.scss new file mode 100644 index 0000000..458eea1 --- /dev/null +++ b/kenji_crosland/app/sass/_reset.scss @@ -0,0 +1,427 @@ +/*! normalize.css v3.0.2 | MIT License | git.io/normalize */ + +/** + * 1. Set default font family to sans-serif. + * 2. Prevent iOS text size adjust after orientation change, without disabling + * user zoom. + */ + +html { + font-family: sans-serif; /* 1 */ + -ms-text-size-adjust: 100%; /* 2 */ + -webkit-text-size-adjust: 100%; /* 2 */ +} + +/** + * Remove default margin. + */ + +body { + margin: 0; +} + +/* HTML5 display definitions + ========================================================================== */ + +/** + * Correct `block` display not defined for any HTML5 element in IE 8/9. + * Correct `block` display not defined for `details` or `summary` in IE 10/11 + * and Firefox. + * Correct `block` display not defined for `main` in IE 11. + */ + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; +} + +/** + * 1. Correct `inline-block` display not defined in IE 8/9. + * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. + */ + +audio, +canvas, +progress, +video { + display: inline-block; /* 1 */ + vertical-align: baseline; /* 2 */ +} + +/** + * Prevent modern browsers from displaying `audio` without controls. + * Remove excess height in iOS 5 devices. + */ + +audio:not([controls]) { + display: none; + height: 0; +} + +/** + * Address `[hidden]` styling not present in IE 8/9/10. + * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. + */ + +[hidden], +template { + display: none; +} + +/* Links + ========================================================================== */ + +/** + * Remove the gray background color from active links in IE 10. + */ + +a { + background-color: transparent; +} + +/** + * Improve readability when focused and also mouse hovered in all browsers. + */ + +a:active, +a:hover { + outline: 0; +} + +/* Text-level semantics + ========================================================================== */ + +/** + * Address styling not present in IE 8/9/10/11, Safari, and Chrome. + */ + +abbr[title] { + border-bottom: 1px dotted; +} + +/** + * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. + */ + +b, +strong { + font-weight: bold; +} + +/** + * Address styling not present in Safari and Chrome. + */ + +dfn { + font-style: italic; +} + +/** + * Address variable `h1` font-size and margin within `section` and `article` + * contexts in Firefox 4+, Safari, and Chrome. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +/** + * Address styling not present in IE 8/9. + */ + +mark { + background: #ff0; + color: #000; +} + +/** + * Address inconsistent and variable font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` affecting `line-height` in all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +/* Embedded content + ========================================================================== */ + +/** + * Remove border when inside `a` element in IE 8/9/10. + */ + +img { + border: 0; +} + +/** + * Correct overflow not hidden in IE 9/10/11. + */ + +svg:not(:root) { + overflow: hidden; +} + +/* Grouping content + ========================================================================== */ + +/** + * Address margin not present in IE 8/9 and Safari. + */ + +figure { + margin: 1em 40px; +} + +/** + * Address differences between Firefox and other browsers. + */ + +hr { + -moz-box-sizing: content-box; + box-sizing: content-box; + height: 0; +} + +/** + * Contain overflow in all browsers. + */ + +pre { + overflow: auto; +} + +/** + * Address odd `em`-unit font size rendering in all browsers. + */ + +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} + +/* Forms + ========================================================================== */ + +/** + * Known limitation: by default, Chrome and Safari on OS X allow very limited + * styling of `select`, unless a `border` property is set. + */ + +/** + * 1. Correct color not being inherited. + * Known issue: affects color of disabled elements. + * 2. Correct font properties not being inherited. + * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. + */ + +button, +input, +optgroup, +select, +textarea { + color: inherit; /* 1 */ + font: inherit; /* 2 */ + margin: 0; /* 3 */ +} + +/** + * Address `overflow` set to `hidden` in IE 8/9/10/11. + */ + +button { + overflow: visible; +} + +/** + * Address inconsistent `text-transform` inheritance for `button` and `select`. + * All other form control elements do not inherit `text-transform` values. + * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. + * Correct `select` style inheritance in Firefox. + */ + +button, +select { + text-transform: none; +} + +/** + * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` + * and `video` controls. + * 2. Correct inability to style clickable `input` types in iOS. + * 3. Improve usability and consistency of cursor style between image-type + * `input` and others. + */ + +button, +html input[type="button"], /* 1 */ +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; /* 2 */ + cursor: pointer; /* 3 */ +} + +/** + * Re-set default cursor for disabled elements. + */ + +button[disabled], +html input[disabled] { + cursor: default; +} + +/** + * Remove inner padding and border in Firefox 4+. + */ + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} + +/** + * Address Firefox 4+ setting `line-height` on `input` using `!important` in + * the UA stylesheet. + */ + +input { + line-height: normal; +} + +/** + * It's recommended that you don't attempt to style these elements. + * Firefox's implementation doesn't respect box-sizing, padding, or width. + * + * 1. Address box sizing set to `content-box` in IE 8/9/10. + * 2. Remove excess padding in IE 8/9/10. + */ + +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Fix the cursor style for Chrome's increment/decrement buttons. For certain + * `font-size` values of the `input`, it causes the cursor style of the + * decrement button to change from `default` to `text`. + */ + +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +/** + * 1. Address `appearance` set to `searchfield` in Safari and Chrome. + * 2. Address `box-sizing` set to `border-box` in Safari and Chrome + * (include `-moz` to future-proof). + */ + +input[type="search"] { + -webkit-appearance: textfield; /* 1 */ + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; /* 2 */ + box-sizing: content-box; +} + +/** + * Remove inner padding and search cancel button in Safari and Chrome on OS X. + * Safari (but not Chrome) clips the cancel button when the search input has + * padding (and `textfield` appearance). + */ + +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * Define consistent border, margin, and padding. + */ + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +/** + * 1. Correct `color` not being inherited in IE 8/9/10/11. + * 2. Remove padding so people aren't caught out if they zero out fieldsets. + */ + +legend { + border: 0; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Remove default vertical scrollbar in IE 8/9/10/11. + */ + +textarea { + overflow: auto; +} + +/** + * Don't inherit the `font-weight` (applied by a rule above). + * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. + */ + +optgroup { + font-weight: bold; +} + +/* Tables + ========================================================================== */ + +/** + * Remove most spacing between table cells. + */ + +table { + border-collapse: collapse; + border-spacing: 0; +} + +td, +th { + padding: 0; +} diff --git a/kenji_crosland/app/sass/application.scss b/kenji_crosland/app/sass/application.scss new file mode 100644 index 0000000..b2c50cb --- /dev/null +++ b/kenji_crosland/app/sass/application.scss @@ -0,0 +1,7 @@ +@import 'colors/colors'; +@import 'reset'; +@import 'base'; +@import 'buttons/buttons'; +@import 'forms/forms'; +@import 'modules/module'; +@import 'layouts/layouts'; diff --git a/kenji_crosland/app/sass/buttons/_add-ingredient-button.scss b/kenji_crosland/app/sass/buttons/_add-ingredient-button.scss new file mode 100644 index 0000000..e5867c6 --- /dev/null +++ b/kenji_crosland/app/sass/buttons/_add-ingredient-button.scss @@ -0,0 +1,3 @@ +.add-ingredient { + margin-top: 1em; +} diff --git a/kenji_crosland/app/sass/buttons/_buttons.scss b/kenji_crosland/app/sass/buttons/_buttons.scss new file mode 100644 index 0000000..e470c4d --- /dev/null +++ b/kenji_crosland/app/sass/buttons/_buttons.scss @@ -0,0 +1,3 @@ +@import 'default-button'; +@import 'add-ingredient-button'; +@import 'state'; diff --git a/kenji_crosland/app/sass/buttons/_default-button.scss b/kenji_crosland/app/sass/buttons/_default-button.scss new file mode 100644 index 0000000..098aee8 --- /dev/null +++ b/kenji_crosland/app/sass/buttons/_default-button.scss @@ -0,0 +1,5 @@ +.btn { + border: none; + background: $primary2; + color: $light-background; +} diff --git a/kenji_crosland/app/sass/buttons/_state.scss b/kenji_crosland/app/sass/buttons/_state.scss new file mode 100644 index 0000000..39853f1 --- /dev/null +++ b/kenji_crosland/app/sass/buttons/_state.scss @@ -0,0 +1,5 @@ +button[disabled] { + background-color: map-get($button, disabled-background); + border-color: map-get($button, disabled-border); + color: map-get($button, disabled-text); +} diff --git a/kenji_crosland/app/sass/colors/_button.scss b/kenji_crosland/app/sass/colors/_button.scss new file mode 100644 index 0000000..5970ee2 --- /dev/null +++ b/kenji_crosland/app/sass/colors/_button.scss @@ -0,0 +1,5 @@ +$button: ( + disabled-background: lighten($primary2, 20%), + disabled-border: lighten($primary2, 50%), + disabled-text: $light-shading +); diff --git a/kenji_crosland/app/sass/colors/_colors.scss b/kenji_crosland/app/sass/colors/_colors.scss new file mode 100644 index 0000000..7486d5e --- /dev/null +++ b/kenji_crosland/app/sass/colors/_colors.scss @@ -0,0 +1,2 @@ +@import 'theme'; +@import 'button'; diff --git a/kenji_crosland/app/sass/colors/_theme.scss b/kenji_crosland/app/sass/colors/_theme.scss new file mode 100644 index 0000000..20242f4 --- /dev/null +++ b/kenji_crosland/app/sass/colors/_theme.scss @@ -0,0 +1,19 @@ +//Color Scheme +//https://color.adobe.com/pomegranate-explosion-color-theme-7287945/ +//'Pomegranate Explosion' Color Palate +$dark-turquoise: #63A69F; +$linen: #FBF5E5; +$wheat: #F2E1AC; +$salmon: #F2836B; +$carnation: #F2594B; +$cardinal-red: #CD2C24; +$light-gray: #eef; + +//Theme variables +$light-shading: $light-gray; +$background: $wheat; +$light-background: $linen; +$primary1: $salmon; +$primary2: $dark-turquoise; +$accent: $carnation; +$strong-accent: $cardinal-red; diff --git a/kenji_crosland/app/sass/forms/_forms.scss b/kenji_crosland/app/sass/forms/_forms.scss new file mode 100644 index 0000000..fbd6859 --- /dev/null +++ b/kenji_crosland/app/sass/forms/_forms.scss @@ -0,0 +1 @@ +@import 'edit-recipe/edit-recipe-form'; diff --git a/kenji_crosland/app/sass/forms/edit-recipe/_edit-recipe-form.scss b/kenji_crosland/app/sass/forms/edit-recipe/_edit-recipe-form.scss new file mode 100644 index 0000000..e898297 --- /dev/null +++ b/kenji_crosland/app/sass/forms/edit-recipe/_edit-recipe-form.scss @@ -0,0 +1,16 @@ +.edit-recipe-form { + label { + font-weight: 600; + } + input { + width: 100%; + } + fieldset { + padding: 1em; + margin-top: 1em; + margin-bottom: 1em; + input { + width:75%; + } + } +} diff --git a/kenji_crosland/app/sass/layouts/_grid.scss b/kenji_crosland/app/sass/layouts/_grid.scss new file mode 100644 index 0000000..b82885c --- /dev/null +++ b/kenji_crosland/app/sass/layouts/_grid.scss @@ -0,0 +1,142 @@ +body { + padding: 1em; +} +/* +* Skeleton V2.0.4 +* Copyright 2014, Dave Gamache +* www.getskeleton.com +* Free to use under the MIT license. +* http://www.opensource.org/licenses/mit-license.php +* 12/29/2014 +*/ + + +/* Table of contents +–––––––––––––––––––––––––––––––––––––––––––––––––– +- Grid +- Base Styles +- Typography +- Links +- Buttons +- Forms +- Lists +- Code +- Tables +- Spacing +- Utilities +- Clearing +- Media Queries +*/ + + +/* Grid +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +.container { + position: relative; + width: 100%; + max-width: 960px; + margin: 0 auto; + padding: 0 20px; + box-sizing: border-box; } +.column, +.columns { + width: 100%; + float: left; + box-sizing: border-box; } + +/* For devices larger than 400px */ +@media (min-width: 400px) { + .container { + width: 85%; + padding: 0; } +} + +/* For devices larger than 550px */ +@media (min-width: 550px) { + .container { + width: 80%; } + .column, + .columns { + margin-left: 4%; } + .column:first-child, + .columns:first-child { + margin-left: 0; } + + .one.column, + .one.columns { width: 4.66666666667%; } + .two.columns { width: 13.3333333333%; } + .three.columns { width: 22%; } + .four.columns { width: 30.6666666667%; } + .five.columns { width: 39.3333333333%; } + .six.columns { width: 48%; } + .seven.columns { width: 56.6666666667%; } + .eight.columns { width: 65.3333333333%; } + .nine.columns { width: 74.0%; } + .ten.columns { width: 82.6666666667%; } + .eleven.columns { width: 91.3333333333%; } + .twelve.columns { width: 100%; margin-left: 0; } + + .one-third.column { width: 30.6666666667%; } + .two-thirds.column { width: 65.3333333333%; } + + .one-half.column { width: 48%; } + + /* Offsets */ + .offset-by-one.column, + .offset-by-one.columns { margin-left: 8.66666666667%; } + .offset-by-two.column, + .offset-by-two.columns { margin-left: 17.3333333333%; } + .offset-by-three.column, + .offset-by-three.columns { margin-left: 26%; } + .offset-by-four.column, + .offset-by-four.columns { margin-left: 34.6666666667%; } + .offset-by-five.column, + .offset-by-five.columns { margin-left: 43.3333333333%; } + .offset-by-six.column, + .offset-by-six.columns { margin-left: 52%; } + .offset-by-seven.column, + .offset-by-seven.columns { margin-left: 60.6666666667%; } + .offset-by-eight.column, + .offset-by-eight.columns { margin-left: 69.3333333333%; } + .offset-by-nine.column, + .offset-by-nine.columns { margin-left: 78.0%; } + .offset-by-ten.column, + .offset-by-ten.columns { margin-left: 86.6666666667%; } + .offset-by-eleven.column, + .offset-by-eleven.columns { margin-left: 95.3333333333%; } + + .offset-by-one-third.column, + .offset-by-one-third.columns { margin-left: 34.6666666667%; } + .offset-by-two-thirds.column, + .offset-by-two-thirds.columns { margin-left: 69.3333333333%; } + + .offset-by-one-half.column, + .offset-by-one-half.columns { margin-left: 52%; } + +} + + +/* Media Queries +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +/* +Note: The best way to structure the use of media queries is to create the queries +near the relevant code. For example, if you wanted to change the styles for buttons +on small devices, paste the mobile query code up in the buttons section and style it +there. +*/ + + +/* Larger than mobile */ +@media (min-width: 400px) {} + +/* Larger than phablet (also point when grid becomes active) */ +@media (min-width: 550px) {} + +/* Larger than tablet */ +@media (min-width: 750px) {} + +/* Larger than desktop */ +@media (min-width: 1000px) {} + +/* Larger than Desktop HD */ +@media (min-width: 1200px) {} diff --git a/kenji_crosland/app/sass/layouts/_layouts.scss b/kenji_crosland/app/sass/layouts/_layouts.scss new file mode 100644 index 0000000..4becf10 --- /dev/null +++ b/kenji_crosland/app/sass/layouts/_layouts.scss @@ -0,0 +1 @@ +@import 'grid'; diff --git a/kenji_crosland/app/sass/modules/_module.scss b/kenji_crosland/app/sass/modules/_module.scss new file mode 100644 index 0000000..69d005c --- /dev/null +++ b/kenji_crosland/app/sass/modules/_module.scss @@ -0,0 +1,3 @@ +@import "main-headline/module_main-headline"; +@import "card/module_card"; +@import "recipe-list/module_recipe-list"; diff --git a/kenji_crosland/app/sass/modules/card/_mixin.scss b/kenji_crosland/app/sass/modules/card/_mixin.scss new file mode 100644 index 0000000..379dde5 --- /dev/null +++ b/kenji_crosland/app/sass/modules/card/_mixin.scss @@ -0,0 +1,6 @@ +@mixin card-headline($background-color) { + padding: .5em; + color: #fff; + margin-top: 0; + background-color: $background-color; +} diff --git a/kenji_crosland/app/sass/modules/card/_module_card.scss b/kenji_crosland/app/sass/modules/card/_module_card.scss new file mode 100644 index 0000000..a5fa89a --- /dev/null +++ b/kenji_crosland/app/sass/modules/card/_module_card.scss @@ -0,0 +1,19 @@ +@import 'mixin'; + +.card { + h2 { + @include card-headline($primary2); + } + h3 { + @include card-headline($primary1); + } + .card-top { + padding: 0; + } + .card-bottom { + padding: 1em; + } + background-color: $background; + margin-top: 1em; + box-shadow: 3px 3px 1px #bbb; +} diff --git a/kenji_crosland/app/sass/modules/main-headline/_module_main-headline.scss b/kenji_crosland/app/sass/modules/main-headline/_module_main-headline.scss new file mode 100644 index 0000000..9b02003 --- /dev/null +++ b/kenji_crosland/app/sass/modules/main-headline/_module_main-headline.scss @@ -0,0 +1,5 @@ +.main-headline { + font-family: 'Dancing Script', cursive; + margin-top: 0; + color: $accent; +} diff --git a/kenji_crosland/app/sass/modules/recipe-list/_module_recipe-list.scss b/kenji_crosland/app/sass/modules/recipe-list/_module_recipe-list.scss new file mode 100644 index 0000000..75c140a --- /dev/null +++ b/kenji_crosland/app/sass/modules/recipe-list/_module_recipe-list.scss @@ -0,0 +1,9 @@ +ul.recipe-list { + li { + list-style-type: none; + } + li button { + margin-bottom: .4em; + } + margin: 0 0 0 -1.75em; +} diff --git a/kenji_crosland/app/templates/card_template.html b/kenji_crosland/app/templates/card_template.html new file mode 100644 index 0000000..9ca1386 --- /dev/null +++ b/kenji_crosland/app/templates/card_template.html @@ -0,0 +1,11 @@ +
+
+

{{headerText}}

+

{{headerText}}

+

{{headerText}}

+

{{headerText}}

+
+
+
+
+
diff --git a/kenji_crosland/app/templates/recipe_form_template.html b/kenji_crosland/app/templates/recipe_form_template.html new file mode 100644 index 0000000..8e937d6 --- /dev/null +++ b/kenji_crosland/app/templates/recipe_form_template.html @@ -0,0 +1,14 @@ +
+ + +
+ Ingredients: +
+ + +
+ +
+ + +
diff --git a/kenji_crosland/gulpfile.js b/kenji_crosland/gulpfile.js index 2d415d4..fb61990 100644 --- a/kenji_crosland/gulpfile.js +++ b/kenji_crosland/gulpfile.js @@ -1,8 +1,13 @@ var gulp = require('gulp'); var jshint = require('gulp-jshint'); var mocha = require('gulp-mocha'); +var webpack = require('webpack-stream'); +var minifyCss = require('gulp-minify-css'); +var gulpWatch = require('gulp-watch'); +var sass = require('gulp-sass'); +var maps = require('gulp-sourcemaps'); var appFiles = ['index.js','models/**/*.js', 'routes/**/*.js', 'gulpfile.js', 'lib/**/*.js']; -var testFiles = ['./test/**/*.js']; +var testFiles = ['./test/test.js', './test/recipes_controller_test.js']; gulp.task('jshint:test', function(){ return gulp.src(testFiles) @@ -31,5 +36,45 @@ gulp.task('mocha', ['jshint'], function(){ .pipe(mocha({reporter: 'spec'})); }); +gulp.task('static:dev', function(){ + gulp.src('app/**/*.html') + .pipe(gulp.dest('build/')); +}); + +gulp.task('sass:dev', function(){ + gulp.src('./app/sass/**/*.scss') + .pipe(maps.init()) + .pipe(sass().on('error', sass.logError)) + .pipe(minifyCss()) + .pipe(maps.write('./')) + .pipe(gulp.dest('build/')); +}); + +gulp.task('sass:watch', function() { + gulp.watch(['./app/sass/**/*.scss', './app/index.html'], ['sass:dev','static:dev']); +}); + +gulp.task('webpack:dev', function(){ + gulp.src('app/js/entry.js') + .pipe(webpack({ + output: { + filename: 'bundle.js' + } + })) + .pipe(gulp.dest('build/')); +}); + +gulp.task('webpack:test', function(){ + gulp.src('test/client/test_entry.js') + .pipe(webpack({ + output: { + filename: 'test_bundle.js' + } + })) + .pipe(gulp.dest('test/client/')); +}); + +gulp.task('static:watch'); +gulp.task('build:dev', ['webpack:dev', 'static:dev', 'sass:dev']); gulp.task('jshint', ['jshint:test', 'jshint:app']); -gulp.task('default', ['jshint', 'mocha']); +gulp.task('default', ['build:dev', 'jshint', 'mocha']); diff --git a/kenji_crosland/karma.conf.js b/kenji_crosland/karma.conf.js new file mode 100644 index 0000000..394be09 --- /dev/null +++ b/kenji_crosland/karma.conf.js @@ -0,0 +1,69 @@ +// Karma configuration +// Generated on Wed Dec 02 2015 18:17:05 GMT-0800 (PST) + +module.exports = function(config) { + config.set({ + + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: '', + + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['jasmine'], + + + // list of files / patterns to load in the browser + files: [ + 'test/client/test_bundle.js' + ], + + + // list of files to exclude + exclude: [ + ], + + + // preprocess matching files before serving them to the browser + // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + preprocessors: { + }, + + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ['progress'], + + + // web server port + port: 9876, + + + // enable / disable colors in the output (reporters and logs) + colors: true, + + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: false, + + + // start these browsers + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: ['PhantomJS'], + + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: true, + + // Concurrency level + // how many browser should be started simultanous + concurrency: Infinity + }) +} diff --git a/kenji_crosland/lib/basic_http_authentication.js b/kenji_crosland/lib/basic_http_authentication.js new file mode 100644 index 0000000..1fc4338 --- /dev/null +++ b/kenji_crosland/lib/basic_http_authentication.js @@ -0,0 +1,18 @@ +//Got help from in class code +module.exports = function(req, res, next) { + try { + //Could be chained into one line + var authString = req.headers.authorization; + var basicString = authString.split(' ')[1]; + var basicBuffer = new Buffer(basicString, 'base64'); + var authArray = basicBuffer.toString().split(':'); + req.auth = { + username: authArray[0], + password: authArray[1] + }; + next(); + } catch (e) { + console.log(e); + return res.status(401).json({msg: 'could not authentiCat'}); + } +}; diff --git a/kenji_crosland/lib/eat_auth.js b/kenji_crosland/lib/eat_auth.js new file mode 100644 index 0000000..4053e21 --- /dev/null +++ b/kenji_crosland/lib/eat_auth.js @@ -0,0 +1,31 @@ +//Got help from in class code +var eat = require('eat'); +var User = require(__dirname + '/../models/user'); + +module.exports = exports = function(req, res, next) { + var token = req.headers.token || (req.body)? req.body.token : ''; + if (!token){ + return res.json({msg: 'authentiCat seyz noe!1111@! and is watching youuuu!'}); + } + eat.decode(token, process.env.APP_SECRET, function(err, decoded){ + if (err) { + console.log(err); + return res.json({msg: 'authenitCat seyz noe!1111@!'}); + } + + User.findOne({_id: decoded.id}, function(err, user){ + if (err) { + console.log(err); + return res.status(401).json({msg: 'authentiCat seyz noe121!'}); + } + + if (!user) { + console.log(err); + return res.status(401).json({msg: 'authentiCat seyz noe121!'}); + } + + req.user = user; + next(); + }); + }); +}; diff --git a/kenji_crosland/models/recipe.js b/kenji_crosland/models/recipe.js index 9c98069..39042b0 100644 --- a/kenji_crosland/models/recipe.js +++ b/kenji_crosland/models/recipe.js @@ -5,6 +5,8 @@ var reviewSchema = require(__dirname + '/review.js').reviewSchema; var recipeSchema = mongoose.Schema({ title: String, ingredients: Array, + user: String, + userId: String, reviews: [reviewSchema] }); diff --git a/kenji_crosland/models/user.js b/kenji_crosland/models/user.js new file mode 100644 index 0000000..fe53c62 --- /dev/null +++ b/kenji_crosland/models/user.js @@ -0,0 +1,32 @@ +//Got validation help here: http://nraj.tumblr.com/post/38706353543/handling-uniqueness-validation-in-mongomongoose + +var mongoose = require('mongoose'); +var bcrypt = require('bcrypt'); +var eat = require('eat'); +var userSchema = new mongoose.Schema({ +//Adding it at the beginning so it doesn't get deleted. + username: {type: String, required: true, unique: true, trim: true}, + auth: { + basic:{ + username: String, + password: String + } + } +}); + +userSchema.methods.hashPassword = function(password) { + var hash = this.auth.basic.password = bcrypt.hashSync(password, 8); + return hash; +}; + +userSchema.methods.checkPassword = function(password) { + return bcrypt.compareSync(password, this.auth.basic.password); + +}; + +userSchema.methods.generateToken = function(callback){ + var id = this._id; + eat.encode({id: id}, process.env.APP_SECRET, callback); +}; + +module.exports = mongoose.model('User', userSchema); diff --git a/kenji_crosland/package.json b/kenji_crosland/package.json index 534ce00..f8a80c7 100644 --- a/kenji_crosland/package.json +++ b/kenji_crosland/package.json @@ -9,17 +9,34 @@ "author": "Kenji Crosland", "license": "MIT", "dependencies": { + "bcrypt": "latest", "body-parser": "^1.14.1", + "eat": "^0.1.1", "express": "^4.13.3", "mongoose": "^4.2.5" }, "devDependencies": { + "angular": "^1.4.8", + "angular-mocks": "^1.4.8", "chai": "^3.4.1", "chai-http": "^1.0.0", "gulp": "^3.9.0", - "gulp-jshint": "^1.12.0", + "gulp-concat-css": "^2.2.0", + "gulp-jshint": "^2.0.0", + "gulp-minify-css": "^1.2.2", "gulp-mocha": "^2.1.3", + "gulp-sass": "^2.1.0", + "gulp-sourcemaps": "^1.6.0", + "gulp-watch": "^4.3.5", + "jasmine-core": "^2.3.4", + "jshint": "^2.8.0", "jshint-stylish": "^2.0.1", - "mocha": "^2.3.3" + "karma": "^0.13.15", + "karma-jasmine": "^0.3.6", + "karma-phantomjs-launcher": "^0.2.1", + "mocha": "^2.3.3", + "phantomjs": "^1.9.19", + "sass-list-maps": "^1.0.0-b", + "webpack-stream": "^2.1.1" } } diff --git a/kenji_crosland/routes/auth_routes.js b/kenji_crosland/routes/auth_routes.js new file mode 100644 index 0000000..fbc82df --- /dev/null +++ b/kenji_crosland/routes/auth_routes.js @@ -0,0 +1,53 @@ +var express = require('express'); +var jsonParser = require('body-parser').json(); +var basicHttp = require(__dirname + '/../lib/basic_http_authentication'); +var User = require(__dirname + '/../models/user'); + +var authRouter = module.exports = exports = express.Router(); + +authRouter.post('/signup', jsonParser, function(req, res){ + var user = new User(); + user.username = req.body.username; + user.auth.basic.username = req.body.username; + user.hashPassword(req.body.password); + + user.save(function(err,data){ + if (err && (11000 === err.code || 11001 === err.code)){ + return res.status(401).json({msg: 'authentiCat seayz this user already exists'}); + } + if (err) throw err; + + user.generateToken(function(err, token){ + if (err) throw err; + res.json({token: token}); + }); + + }); +}); + +authRouter.get('/signin', basicHttp, function(req, res){ + if (!(req.auth.username && req.auth.password)) { + console.log('no basic auth provided'); + return res.status(401).json({msg: 'authentiCat seayz noe!'}); + } + + User.findOne({'auth.basic.username': req.auth.username}, function(err, user){ + if (err) { + console.log('no basic auth provided'); + return res.status(401).json({msg: 'authentiCat seayz noe!'}); + } + if (!user){ + console.log('you are not a user'); + return res.status(401).json({msg: 'authentiCat seayz u r not a urzr!'}); + } + if (!user.checkPassword(req.auth.password)) { + console.log('the password donut match'); + return res.status(401).json({msg: 'authentiCat seeeez noe!'}); + } + user.generateToken(function(err, token){ + if (err) throw err; + + res.json({token: token}); + }); + }); +}); diff --git a/kenji_crosland/routes/recipes_routes.js b/kenji_crosland/routes/recipes_routes.js index bd75e1f..e80ee87 100644 --- a/kenji_crosland/routes/recipes_routes.js +++ b/kenji_crosland/routes/recipes_routes.js @@ -3,9 +3,18 @@ var express = require('express'); var bodyParser = require('body-parser'); var Recipe = require(__dirname + '/../models/recipe').Recipe; var Review = require(__dirname + '/../models/review').Review; +var eatAuth = require(__dirname + '/../lib/eat_auth'); + var recipeRouter = module.exports = express.Router(); -recipeRouter.get('/recipes', function(req, res){ +recipeRouter.get('/recipes', bodyParser.json(), function(req, res){ + Recipe.find({userId: req.user._id}, function(err, data){ + if (err) throw err; + res.json(data); + }); +}); + +recipeRouter.get('/allrecipes', function(req, res){ Recipe.find({}, function(err, data){ if (err) throw err; res.json(data); @@ -14,6 +23,8 @@ recipeRouter.get('/recipes', function(req, res){ recipeRouter.post('/recipes', bodyParser.json(), function(req, res){ var newRecipe = new Recipe(req.body); + //newRecipe.userId = req.user._id; + //newRecipe.user = req.user.username; newRecipe.save(function(err, data){ if (err) throw err; res.json(data); @@ -37,7 +48,7 @@ recipeRouter.put('/recipes/:id', bodyParser.json(), function(req, res){ }); }); -recipeRouter.delete('/recipes/:id', function(req, res){ +recipeRouter.delete('/recipes/:id', bodyParser.json(), function(req, res){ Recipe.remove({_id: req.params.id}, function(err){ if (err) return err; res.json({msg: 'Recipe deleted!'}); diff --git a/kenji_crosland/server.js b/kenji_crosland/server.js index d5d7ff4..d306bfb 100644 --- a/kenji_crosland/server.js +++ b/kenji_crosland/server.js @@ -1,17 +1,23 @@ //Made with help from in class code -var mongoose = require('mongoose') +var mongoose = require('mongoose'); var express = require('express'); +var fs = require('fs'); var app = express(); var recipesRouter = require(__dirname + '/routes/recipes_routes'); var ingredientsRouter = require(__dirname + '/routes/ingredients_routes'); - - +var authRouter = require(__dirname + '/routes/auth_routes'); +process.env.APP_SECRET = process.env.APP_SECRET = "changeme" //Use secret key mongoose.connect(process.env.MONGOLAB_URI || 'mongodb://localhost/recipe_database'); app.use(recipesRouter); app.use(ingredientsRouter); -app.use(express.static(__dirname + '/public')); +app.use(authRouter); +app.use(express.static(__dirname + '/build')); + +app.use(function(req, res){ + res.status(404).send('could not find file'); +}); app.listen(process.env.PORT || 3000, function(){ console.log('server up'); diff --git a/kenji_crosland/test/client/recipes_controller_test.js b/kenji_crosland/test/client/recipes_controller_test.js new file mode 100644 index 0000000..f39b802 --- /dev/null +++ b/kenji_crosland/test/client/recipes_controller_test.js @@ -0,0 +1,88 @@ +require(__dirname + '/../../app/js/entry'); +require('angular-mocks'); + +describe('recipes controller', function() { + var $httpBackend; + var $ControllerConstructor; + var $scope; + + beforeEach(angular.mock.module('recipeApp')); + + beforeEach(angular.mock.inject(function($rootScope, $controller){ + $scope = $rootScope.$new(); + $ControllerConstructor = $controller; + })); + + it('should be able to create a controller', function() { + var controller = $ControllerConstructor('RecipesController', {$scope: $scope}); + expect(typeof $scope).toBe('object'); + expect(typeof controller).toBe('object'); + expect(Array.isArray($scope.recipes)).toBe(true); + }); + + it('should be able to cancel editing', function(){ + var controller = $ControllerConstructor('RecipesController', {$scope: $scope}); + $scope.recipes.push({_id:1, title: 'test recipe'}); + var recipe = $scope.recipes[0]; + expect(recipe.title).toBe('test recipe'); + $scope.makeCopy(recipe); + expect(recipe.editing).toBe(true); + expect($scope.editing[recipe._id]).toEqual(recipe); + $scope.cancelEditing(recipe); + expect(recipe.editing).toBe(false); + expect($scope.editing[recipe._id]).toBe(undefined); + }); + + describe('REST requests', function() { + beforeEach(angular.mock.inject(function(_$httpBackend_, $rootScope){ + $httpBackend = _$httpBackend_; + $scope = $rootScope.$new(); + $ControllerConstructor('RecipesController', {$scope: $scope}) + })); + + afterEach(function() { + $httpBackend.verifyNoOutstandingExpectation(); + $httpBackend.verifyNoOutstandingRequest(); + }); + + it('should make a GET request when getAll() is called', function(){ + $httpBackend.expectGET('/allrecipes').respond(200, [{title: 'test recipe'}]); + $scope.getAll(); + $httpBackend.flush(); + expect($scope.recipes[0].title).toBe('test recipe'); + }); + + it('should be able to create a recipe', function() { + $httpBackend.expectPOST('/recipes').respond(200, {title: 'Hipster Sriracha Tacos'}); + expect($scope.recipes.length).toBe(0); + $scope.create({title: 'Hipster Sriracha Tacos'}); + $httpBackend.flush(); + expect($scope.recipes[0].title).toBe('Hipster Sriracha Tacos'); + }); + + it('should be able to update a recipe', function(){ + $scope.recipes.push({title: 'hello'}); + expect($scope.recipes[0].title).toBe('hello'); + $scope.newRecipe = angular.copy($scope.recipes[0]); + $scope.newRecipe.title = 'Spicy Hipster Sriracha Tacos'; + $scope.newRecipe._id = '123'; + $httpBackend.expectPUT('/recipes/' + $scope.newRecipe._id, {"_id":$scope.newRecipe._id,"title":"Spicy Hipster Sriracha Tacos","editing":false}).respond(200, {}); + $scope.update($scope.newRecipe); + $httpBackend.flush(); + expect($scope.newRecipe.editing).toBe(false); + }); + + it('should be able to delete a recipe', function() { + var recipe = $scope.recipes.push({_id:1, title: 'test recipe'}); + expect($scope.recipes[0].title).toBe('test recipe'); + expect($scope.recipes.length).toBe(1); + $httpBackend.expectDELETE('/recipes/' + $scope.recipes._id).respond(200,{}); + $scope.remove(recipe); + $httpBackend.flush(); + expect($scope.recipes.length).toBe(0); + }); + + }); +}); + + diff --git a/kenji_crosland/test/client/test_entry.js b/kenji_crosland/test/client/test_entry.js new file mode 100644 index 0000000..57bb626 --- /dev/null +++ b/kenji_crosland/test/client/test_entry.js @@ -0,0 +1 @@ +require('./recipes_controller_test'); diff --git a/kenji_crosland/test/test.js b/kenji_crosland/test/test.js index 7eb7d59..6f5adba 100644 --- a/kenji_crosland/test/test.js +++ b/kenji_crosland/test/test.js @@ -4,6 +4,7 @@ var chai = require('chai'); var chaiHttp = require('chai-http'); chai.use(chaiHttp); var expect = chai.expect; +var User = require(__dirname + '/../models/user'); process.env.MONGOLAB_URI = 'mongodb://localhost/recipe_test'; @@ -11,71 +12,88 @@ require(__dirname + '/../server'); var mongoose = require('mongoose'); var Recipe = require(__dirname + '/../models/recipe').Recipe; -describe('recipe routes', function(){ +describe('All routes on on the recipe app', function(){ + var token = ""; + after(function(done){ mongoose.connection.db.dropDatabase(function(){ done(); }); }); - it('should create a recipe with a POST request',function(done){ - var recipeData = {title:'Tacos', ingredients:['pork', 'cumin', 'chili powder', 'garlic', 'onions', 'tomatoes']}; - chai.request('http://localhost:3000') - .post('/recipes') - .send(recipeData) - .end(function(err,res){ + describe('authorization routes', function(done){ + before(function(done){ + var userData = {username: 'Iron Chef', password:'foobar123'}; + chai.request('http://localhost:3000') + .post('/signup') + .send(userData) + .end(function(err, res){ + token = res.body.token; expect(err).to.eql(null); - expect(res.body.title).to.eql('Tacos'); - expect(res.body.ingredients).to.include('tomatoes'); - expect(res.body).to.have.property('_id'); + expect(token).to.not.eql(""); done(); }); }); - it('should get all the recipes with a GET request', function(done){ - chai.request('http://localhost:3000') - .get('/recipes') - .end(function(err, res){ - expect(err).to.eql(null); - expect(Array.isArray(res.body)).to.eql(true); - done(); - }); - }); - - describe('a test recipe', function(){ - beforeEach(function(done){ - (new Recipe({title: "Teriyaki", ingredients:['rice', 'chicken', 'soy sauce', 'sugar']})) - .save(function(err, data){ - this.recipe = data; + it('should have created a user with a the post request above', function(done){ + User.findOne({'auth.basic.username': 'Iron Chef'}, function(err, user){ + expect(user).to.not.eql(null); + expect(user.username).to.eql('Iron Chef'); done(); - }.bind(this)); + }); + }); + it('should be able to log in with an existing username', function(done){ + chai.request('http://localhost:3000') + .get('/signin') + .auth('Iron Chef', 'foobar123') + .end(function(err, res){ + expect(err).to.eql(null); + expect(token).to.not.eql(""); + done(); }); + }); - it('should be modified by a PUT request', function(done){ + it('should NOT be able to log in with an existing username', function(done){ chai.request('http://localhost:3000') - .put('/recipes/' + this.recipe._id) - .send({title:"Spicy Teriyaki", ingredients:['rice', 'chicken', 'soy sauce', 'siracha', 'sugar'], reviews: [{text:"It was okay", rating:3}]}) + .get('/signin') + .auth('Iron Chef', 'wrongpassword') .end(function(err, res){ - expect(err).to.eql(null); - expect(res.body.msg).to.eql('Recipe updated!'); + expect(res.body.msg).to.eql('authentiCat seeeez noe!'); done(); }); }); - it('should be modified by a PUT request with a review', function(done){ + it('should NOT create a user with an existing username', function(done){ + var userData = {username: 'Iron Chef', password:'foobar123'}; chai.request('http://localhost:3000') - .put('/recipes/review/' + this.recipe._id) - .send({text:"It was tasty!", rating:5}) + .post('/signup') + .send(userData) .end(function(err, res){ + expect(res.body.msg).to.eql('authentiCat seayz this user already exists'); + done(); + }); + }); + }); + + describe('recipe routes with a valid token', function(){ + + it('should create a recipe with a POST request',function(done){ + var recipeData = {title:'Tacos', ingredients:['pork', 'cumin', 'chili powder', 'garlic', 'onions', 'tomatoes']/*, token: token*/}; + chai.request('http://localhost:3000') + .post('/recipes') + .send(recipeData) + .end(function(err,res){ expect(err).to.eql(null); - expect(res.body.msg).to.eql('Review Added!'); + expect(res.body.title).to.eql('Tacos'); + expect(res.body.ingredients).to.include('tomatoes'); + expect(res.body).to.have.property('_id'); done(); }); }); - it('should return all recipes with a specified ingredient', function(done){ + it('should get all the recipes with a GET request', function(done){ chai.request('http://localhost:3000') - .get('/recipes-made-with/' + this.recipe.ingredients[0]) + .get('/allrecipes') .end(function(err, res){ expect(err).to.eql(null); expect(Array.isArray(res.body)).to.eql(true); @@ -83,14 +101,160 @@ describe('recipe routes', function(){ }); }); - it('should be deleted by a DELETE request', function(done){ + describe('a test recipe', function(){ + beforeEach(function(done){ + (new Recipe({title: "Teriyaki", ingredients:['rice', 'chicken', 'soy sauce', 'sugar']})) + .save(function(err, data){ + this.recipe = data; + done(); + }.bind(this)); + }); + + it('should be modified by a PUT request', function(done){ + chai.request('http://localhost:3000') + .put('/recipes/' + this.recipe._id) + .send({title:"Spicy Teriyaki", ingredients:['rice', 'chicken', 'soy sauce', 'siracha', 'sugar'], reviews: [{text:"It was okay", rating:3}], token: token}) + .end(function(err, res){ + expect(err).to.eql(null); + expect(res.body.msg).to.eql('Recipe updated!'); + done(); + }); + }); + + it('should be modified by a PUT request with a review', function(done){ + chai.request('http://localhost:3000') + .put('/recipes/review/' + this.recipe._id) + .send({text:"It was tasty!", rating:5, token: token}) + .end(function(err, res){ + expect(err).to.eql(null); + expect(res.body.msg).to.eql('Review Added!'); + done(); + }); + }); + + it('should return all recipes with a specified ingredient', function(done){ + chai.request('http://localhost:3000') + .get('/recipes-made-with/' + this.recipe.ingredients[0]) + .end(function(err, res){ + expect(err).to.eql(null); + expect(Array.isArray(res.body)).to.eql(true); + done(); + }); + }); + + it('should be deleted by a DELETE request', function(done){ + chai.request('http://localhost:3000') + .delete('/recipes/' + this.recipe._id) + .send({token: token}) + .end(function(err, res){ + expect(err).to.eql(null); + expect(res.body.msg).to.eql('Recipe deleted!'); + done(); + }); + }); + }); + }); + describe('recipe routes without sending a token', function(){ + + // it('should NOT create a recipe with a POST request',function(done){ + // var recipeData = {title:'Tacos', ingredients:['pork', 'cumin', 'chili powder', 'garlic', 'onions', 'tomatoes']}; + // chai.request('http://localhost:3000') + // .post('/recipes') + // .send(recipeData) + // .end(function(err,res){ + // expect(res.body.msg).to.eql('authentiCat seyz noe!1111@! and is watching youuuu!'); + // done(); + // }); + // }); + + // it('should NOT create a recipe with a POST request and an invalid token',function(done){ + // var recipeData = {title:'Tacos', ingredients:['pork', 'cumin', 'chili powder', 'garlic', 'onions', 'tomatoes'], token: 'Nadda real token'}; + // chai.request('http://localhost:3000') + // .post('/recipes') + // .send(recipeData) + // .end(function(err,res){ + // expect(res.body.msg).to.eql('authenitCat seyz noe!1111@!'); + // done(); + // }); + // }); + + it('should still get all the recipes with a GET request without the token', function(done){ chai.request('http://localhost:3000') - .delete('/recipes/' + this.recipe._id) + .get('/allrecipes') .end(function(err, res){ expect(err).to.eql(null); - expect(res.body.msg).to.eql('Recipe deleted!'); + expect(Array.isArray(res.body)).to.eql(true); done(); }); }); - }); + + describe('a test recipe', function(){ + beforeEach(function(done){ + (new Recipe({title: "Teriyaki", ingredients:['rice', 'chicken', 'soy sauce', 'sugar']})) + .save(function(err, data){ + this.recipe = data; + done(); + }.bind(this)); + }); + + // it('should NOT be modified by a PUT request', function(done){ + // chai.request('http://localhost:3000') + // .put('/recipes/' + this.recipe._id) + // .send({title:"Spicy Teriyaki", ingredients:['rice', 'chicken', 'soy sauce', 'siracha', 'sugar'], reviews: [{text:"It was okay", rating:3}]}) + // .end(function(err, res){ + // expect(res.body.msg).to.eql('authentiCat seyz noe!1111@! and is watching youuuu!'); + // done(); + // }); + // }); + + // it('should NOT be modified by a PUT request with an invalid token', function(done){ + // chai.request('http://localhost:3000') + // .put('/recipes/' + this.recipe._id) + // .send({title:"Spicy Teriyaki", ingredients:['rice', 'chicken', 'soy sauce', 'siracha', 'sugar'], reviews: [{text:"It was okay", rating:3}], token: "Nadda real token"}) + // .end(function(err, res){ + // expect(res.body.msg).to.eql('authenitCat seyz noe!1111@!'); + // done(); + // }); + // }); + + // it('should NOT be modified by a PUT request with a review', function(done){ + // chai.request('http://localhost:3000') + // .put('/recipes/review/' + this.recipe._id) + // .send({text:"It was tasty!", rating:5}) + // .end(function(err, res){ + // expect(res.body.msg).to.eql('authentiCat seyz noe!1111@! and is watching youuuu!'); + // done(); + // }); + // }); + + it('should still return all recipes with a specified ingredient without a token', function(done){ + chai.request('http://localhost:3000') + .get('/recipes-made-with/' + this.recipe.ingredients[0]) + .end(function(err, res){ + expect(err).to.eql(null); + expect(Array.isArray(res.body)).to.eql(true); + done(); + }); + }); + + // it('should NOT be deleted by a DELETE request with an invalid token', function(done){ + // chai.request('http://localhost:3000') + // .delete('/recipes/' + this.recipe._id) + // .send({token: 'Nadda real token'}) + // .end(function(err, res){ + // expect(res.body.msg).to.eql('authenitCat seyz noe!1111@!'); + // done(); + // }); + // }); + + // it('should NOT be deleted by a DELETE request without a token', function(done){ + // chai.request('http://localhost:3000') + // .delete('/recipes/' + this.recipe._id) + // .end(function(err, res){ + // expect(res.body.msg).to.eql('authentiCat seyz noe!1111@! and is watching youuuu!'); + // done(); + // }); + // }); + }); +}); });