diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..22be720781 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# See https://help.github.com/articles/ignoring-files for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile '~/.gitignore_global' + +# Ignore bundler config. +/.bundle + +/coverage +.env + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +/node_modules +/yarn-error.log + +.byebug_history + +.env diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000000..22f928cab8 --- /dev/null +++ b/Gemfile @@ -0,0 +1,81 @@ +source 'https://rubygems.org' + +git_source(:github) do |repo_name| + repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") + "https://github.com/#{repo_name}.git" +end + +gem 'will_paginate', '~> 3.1.0' +gem "omniauth" +gem "omniauth-github" +gem 'colorize' +# Foundation CSS Framework +gem 'foundation-rails', '6.4.1.2' +# Bundle edge Rails instead: gem 'rails', github: 'rails/rails' +gem 'rails', '~> 5.1.4' +# Use postgresql as the database for Active Record +gem 'pg', '~> 0.18' +# Use Puma as the app server +gem 'puma', '~> 3.7' +# Use SCSS for stylesheets +gem 'sass-rails', '~> 5.0' +# Use Uglifier as compressor for JavaScript assets +gem 'uglifier', '>= 1.3.0' +# See https://github.com/rails/execjs#readme for more supported runtimes +# gem 'therubyracer', platforms: :ruby + +# Use CoffeeScript for .coffee assets and views +# gem 'coffee-rails', '~> 4.2' +# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks +gem 'turbolinks', '~> 5' +# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder +gem 'jbuilder', '~> 2.5' +# Use Redis adapter to run Action Cable in production +# gem 'redis', '~> 3.0' +# Use ActiveModel has_secure_password +# gem 'bcrypt', '~> 3.1.7' + +# Use Capistrano for deployment +# gem 'capistrano-rails', group: :development + +group :development, :test do + # Call 'byebug' anywhere in the code to stop execution and get a debugger console + gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] + # Adds support for Capybara system testing and selenium driver + gem 'capybara', '~> 2.13' + gem 'selenium-webdriver' + gem "dotenv-rails" +end + +group :development do + # Access an IRB console on exception pages or by using <%= console %> anywhere in the code. + gem 'web-console', '>= 3.3.0' + gem 'listen', '>= 3.0.5', '< 3.2' + # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring + gem 'spring' + gem 'spring-watcher-listen', '~> 2.0.0' + gem 'guard-minitest', '~> 2.3.2' # https://github.com/guard/guard-minitest + # Colorize minitest output and show failing tests instantly. + gem 'minitest-colorize', git: 'https://github.com/ysbaddaden/minitest-colorize' + gem 'terminal-notifier-guard', '~> 1.6.4' # https://github.com/Springest/terminal-notifier-guard + gem 'terminal-notifier', '~> 1.6.2' # https://github.com/alloy/terminal-notifier + +end + +gem 'simplecov', :require => false, :group => :test + +# Windows does not include zoneinfo files, so bundle the tzinfo-data gem +gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] + +gem 'jquery-turbolinks' +group :development do + gem 'better_errors' + gem 'pry-rails' + gem 'binding_of_caller' + gem 'dotenv-rails' +end + +group :test do + gem 'minitest-rails' + gem 'minitest-reporters' +end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000000..c4877ca1c8 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,304 @@ +GIT + remote: https://github.com/ysbaddaden/minitest-colorize + revision: 81f2dd6aee81c83debc31572608fa89d6d9e1a19 + specs: + minitest-colorize (0.0.6.pre) + minitest (>= 5.2) + +GEM + remote: https://rubygems.org/ + specs: + actioncable (5.1.4) + actionpack (= 5.1.4) + nio4r (~> 2.0) + websocket-driver (~> 0.6.1) + actionmailer (5.1.4) + actionpack (= 5.1.4) + actionview (= 5.1.4) + activejob (= 5.1.4) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 2.0) + actionpack (5.1.4) + actionview (= 5.1.4) + activesupport (= 5.1.4) + rack (~> 2.0) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (5.1.4) + activesupport (= 5.1.4) + builder (~> 3.1) + erubi (~> 1.4) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.3) + activejob (5.1.4) + activesupport (= 5.1.4) + globalid (>= 0.3.6) + activemodel (5.1.4) + activesupport (= 5.1.4) + activerecord (5.1.4) + activemodel (= 5.1.4) + activesupport (= 5.1.4) + arel (~> 8.0) + activesupport (5.1.4) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (~> 0.7) + minitest (~> 5.1) + tzinfo (~> 1.1) + addressable (2.5.2) + public_suffix (>= 2.0.2, < 4.0) + ansi (1.5.0) + arel (8.0.0) + babel-source (5.8.35) + babel-transpiler (0.7.0) + babel-source (>= 4.0, < 6) + execjs (~> 2.0) + better_errors (2.4.0) + coderay (>= 1.0.0) + erubi (>= 1.0.0) + rack (>= 0.9.0) + bindex (0.5.0) + binding_of_caller (0.7.2) + debug_inspector (>= 0.0.1) + builder (3.2.3) + byebug (9.1.0) + capybara (2.15.4) + addressable + mini_mime (>= 0.1.3) + nokogiri (>= 1.3.3) + rack (>= 1.0.0) + rack-test (>= 0.5.4) + xpath (~> 2.0) + childprocess (0.8.0) + ffi (~> 1.0, >= 1.0.11) + coderay (1.1.2) + colorize (0.8.1) + concurrent-ruby (1.0.5) + crass (1.0.2) + debug_inspector (0.0.3) + docile (1.1.5) + dotenv (2.2.1) + dotenv-rails (2.2.1) + dotenv (= 2.2.1) + railties (>= 3.2, < 5.2) + erubi (1.7.0) + execjs (2.7.0) + faraday (0.12.2) + multipart-post (>= 1.2, < 3) + ffi (1.9.18) + formatador (0.2.5) + foundation-rails (6.4.1.2) + railties (>= 3.1.0) + sass (>= 3.3.0, < 3.5) + sprockets-es6 (>= 0.9.0) + globalid (0.4.0) + activesupport (>= 4.2.0) + guard (2.14.1) + formatador (>= 0.2.4) + listen (>= 2.7, < 4.0) + lumberjack (~> 1.0) + nenv (~> 0.1) + notiffany (~> 0.0) + pry (>= 0.9.12) + shellany (~> 0.0) + thor (>= 0.18.1) + guard-minitest (2.3.2) + guard (~> 2.0) + minitest (>= 3.0) + hashie (3.5.6) + i18n (0.9.0) + concurrent-ruby (~> 1.0) + jbuilder (2.7.0) + activesupport (>= 4.2.0) + multi_json (>= 1.2) + jquery-turbolinks (2.1.0) + railties (>= 3.1.0) + turbolinks + json (2.0.2) + jwt (1.5.6) + listen (3.1.5) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + ruby_dep (~> 1.2) + loofah (2.1.1) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + lumberjack (1.0.12) + mail (2.6.6) + mime-types (>= 1.16, < 4) + method_source (0.9.0) + mime-types (3.1) + mime-types-data (~> 3.2015) + mime-types-data (3.2016.0521) + mini_mime (0.1.4) + mini_portile2 (2.3.0) + minitest (5.10.3) + minitest-rails (3.0.0) + minitest (~> 5.8) + railties (~> 5.0) + minitest-reporters (1.1.18) + ansi + builder + minitest (>= 5.0) + ruby-progressbar + multi_json (1.12.2) + multi_xml (0.6.0) + multipart-post (2.0.0) + nenv (0.3.0) + nio4r (2.1.0) + nokogiri (1.8.1) + mini_portile2 (~> 2.3.0) + notiffany (0.1.1) + nenv (~> 0.1) + shellany (~> 0.0) + oauth2 (1.4.0) + faraday (>= 0.8, < 0.13) + jwt (~> 1.0) + multi_json (~> 1.3) + multi_xml (~> 0.5) + rack (>= 1.2, < 3) + omniauth (1.7.1) + hashie (>= 3.4.6, < 3.6.0) + rack (>= 1.6.2, < 3) + omniauth-github (1.3.0) + omniauth (~> 1.5) + omniauth-oauth2 (>= 1.4.0, < 2.0) + omniauth-oauth2 (1.4.0) + oauth2 (~> 1.0) + omniauth (~> 1.2) + pg (0.21.0) + pry (0.11.1) + coderay (~> 1.1.0) + method_source (~> 0.9.0) + pry-rails (0.3.6) + pry (>= 0.10.4) + public_suffix (3.0.0) + puma (3.10.0) + rack (2.0.3) + rack-test (0.7.0) + rack (>= 1.0, < 3) + rails (5.1.4) + actioncable (= 5.1.4) + actionmailer (= 5.1.4) + actionpack (= 5.1.4) + actionview (= 5.1.4) + activejob (= 5.1.4) + activemodel (= 5.1.4) + activerecord (= 5.1.4) + activesupport (= 5.1.4) + bundler (>= 1.3.0) + railties (= 5.1.4) + sprockets-rails (>= 2.0.0) + rails-dom-testing (2.0.3) + activesupport (>= 4.2.0) + nokogiri (>= 1.6) + rails-html-sanitizer (1.0.3) + loofah (~> 2.0) + railties (5.1.4) + actionpack (= 5.1.4) + activesupport (= 5.1.4) + method_source + rake (>= 0.8.7) + thor (>= 0.18.1, < 2.0) + rake (12.1.0) + rb-fsevent (0.10.2) + rb-inotify (0.9.10) + ffi (>= 0.5.0, < 2) + ruby-progressbar (1.9.0) + ruby_dep (1.5.0) + rubyzip (1.2.1) + sass (3.4.25) + sass-rails (5.0.6) + railties (>= 4.0.0, < 6) + sass (~> 3.1) + sprockets (>= 2.8, < 4.0) + sprockets-rails (>= 2.0, < 4.0) + tilt (>= 1.1, < 3) + selenium-webdriver (3.6.0) + childprocess (~> 0.5) + rubyzip (~> 1.0) + shellany (0.0.1) + simplecov (0.15.0) + docile (~> 1.1.0) + json (>= 1.8, < 3) + simplecov-html (~> 0.10.0) + simplecov-html (0.10.2) + spring (2.0.2) + activesupport (>= 4.2) + spring-watcher-listen (2.0.1) + listen (>= 2.7, < 4.0) + spring (>= 1.2, < 3.0) + sprockets (3.7.1) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-es6 (0.9.2) + babel-source (>= 5.8.11) + babel-transpiler + sprockets (>= 3.0.0) + sprockets-rails (3.2.1) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) + terminal-notifier (1.6.3) + terminal-notifier-guard (1.6.4) + thor (0.20.0) + thread_safe (0.3.6) + tilt (2.0.8) + turbolinks (5.0.1) + turbolinks-source (~> 5) + turbolinks-source (5.0.3) + tzinfo (1.2.3) + thread_safe (~> 0.1) + uglifier (3.2.0) + execjs (>= 0.3.0, < 3) + web-console (3.5.1) + actionview (>= 5.0) + activemodel (>= 5.0) + bindex (>= 0.4.0) + railties (>= 5.0) + websocket-driver (0.6.5) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.2) + will_paginate (3.1.6) + xpath (2.1.0) + nokogiri (~> 1.3) + +PLATFORMS + ruby + +DEPENDENCIES + better_errors + binding_of_caller + byebug + capybara (~> 2.13) + colorize + dotenv-rails + foundation-rails (= 6.4.1.2) + guard-minitest (~> 2.3.2) + jbuilder (~> 2.5) + jquery-turbolinks + listen (>= 3.0.5, < 3.2) + minitest-colorize! + minitest-rails + minitest-reporters + omniauth + omniauth-github + pg (~> 0.18) + pry-rails + puma (~> 3.7) + rails (~> 5.1.4) + sass-rails (~> 5.0) + selenium-webdriver + simplecov + spring + spring-watcher-listen (~> 2.0.0) + terminal-notifier (~> 1.6.2) + terminal-notifier-guard (~> 1.6.4) + turbolinks (~> 5) + tzinfo-data + uglifier (>= 1.3.0) + web-console (>= 3.3.0) + will_paginate (~> 3.1.0) + +BUNDLED WITH + 1.16.0.pre.3 diff --git a/Guardfile b/Guardfile new file mode 100644 index 0000000000..a500da00d2 --- /dev/null +++ b/Guardfile @@ -0,0 +1,47 @@ +# A sample Guardfile +# More info at https://github.com/guard/guard#readme + +## Uncomment and set this to only include directories you want to watch +# directories %w(app lib config test spec features) \ +# .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")} + +## Note: if you are using the `directories` clause above and you are not +## watching the project directory ('.'), then you will want to move +## the Guardfile to a watched dir and symlink it back, e.g. +# +# $ mkdir config +# $ mv Guardfile config/ +# $ ln -s config/Guardfile . +# +# and, you'll have to watch "config/Guardfile" instead of "Guardfile" +require 'active_support/inflector' + +guard :minitest do + # with Minitest::Unit + watch(%r{^test/(.*)\/?test_(.*)\.rb$}) + watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "test/#{m[1]}test_#{m[2]}.rb" } + watch(%r{^test/test_helper\.rb$}) { 'test' } + + # with Minitest::Spec + # watch(%r{^spec/(.*)_spec\.rb$}) + # watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } + # watch(%r{^spec/spec_helper\.rb$}) { 'spec' } + + # Rails 4 + watch(%r{^app/(.+)\.rb$}) { |m| "test/#{m[1]}_test.rb" } + watch(%r{^app/controllers/application_controller\.rb$}) { 'test/controllers' } + watch(%r{^app/controllers/(.+)_controller\.rb$}) { |m| "test/integration/#{m[1]}_test.rb" } + watch(%r{^app/views/(.+)_mailer/.+}) { |m| "test/mailers/#{m[1]}_mailer_test.rb" } + watch(%r{^lib/(.+)\.rb$}) { |m| "test/lib/#{m[1]}_test.rb" } + watch(%r{^test/.+_test\.rb$}) + watch(%r{^test/test_helper\.rb$}) { 'test' } + watch(%r{^app/views/(.+)/.+}) { |m| "test/controllers/#{m[1]}_controller_test.rb" } + watch(%r{^test/fixtures/(.+)\.yml}) { |m| "test/models/#{m[1].singularize}_test.rb" } + watch(%r{^test/fixtures/(.+)\.yml}) { |m| "test/controllers/#{m[1]}_controller_test.rb" } + + + # Rails < 4 + # watch(%r{^app/controllers/(.*)\.rb$}) { |m| "test/functional/#{m[1]}_test.rb" } + # watch(%r{^app/helpers/(.*)\.rb$}) { |m| "test/helpers/#{m[1]}_test.rb" } + # watch(%r{^app/models/(.*)\.rb$}) { |m| "test/unit/#{m[1]}_test.rb" } +end diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000000..e85f913914 --- /dev/null +++ b/Rakefile @@ -0,0 +1,6 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require_relative 'config/application' + +Rails.application.load_tasks diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js new file mode 100644 index 0000000000..b16e53d6d5 --- /dev/null +++ b/app/assets/config/manifest.js @@ -0,0 +1,3 @@ +//= link_tree ../images +//= link_directory ../javascripts .js +//= link_directory ../stylesheets .css diff --git a/app/assets/images/.keep b/app/assets/images/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/assets/images/favicon.ico b/app/assets/images/favicon.ico new file mode 100644 index 0000000000..5bdb87641d Binary files /dev/null and b/app/assets/images/favicon.ico differ diff --git a/app/assets/images/treehuggernobackground.png b/app/assets/images/treehuggernobackground.png new file mode 100644 index 0000000000..b47bcf1043 Binary files /dev/null and b/app/assets/images/treehuggernobackground.png differ diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js new file mode 100644 index 0000000000..4f2cc0f55a --- /dev/null +++ b/app/assets/javascripts/application.js @@ -0,0 +1,18 @@ +// This is a manifest file that'll be compiled into application.js, which will include all the files +// listed below. +// +// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's +// vendor/assets/javascripts directory can be referenced here using a relative path. +// +// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the +// compiled file. JavaScript code in this file should be added after the last require_* statement. +// +// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details +// about supported directives. +// +//= require rails-ujs +//= require foundation +//= require turbolinks +//= require_tree . + +$(function(){ $(document).foundation(); }); diff --git a/app/assets/javascripts/cable.js b/app/assets/javascripts/cable.js new file mode 100644 index 0000000000..739aa5f022 --- /dev/null +++ b/app/assets/javascripts/cable.js @@ -0,0 +1,13 @@ +// Action Cable provides the framework to deal with WebSockets in Rails. +// You can generate new channels where WebSocket features live using the `rails generate channel` command. +// +//= require action_cable +//= require_self +//= require_tree ./channels + +(function() { + this.App || (this.App = {}); + + App.cable = ActionCable.createConsumer(); + +}).call(this); diff --git a/app/assets/javascripts/channels/.keep b/app/assets/javascripts/channels/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/assets/javascripts/merchants.js b/app/assets/javascripts/merchants.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/merchants.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/app/assets/javascripts/orders.js b/app/assets/javascripts/orders.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/orders.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/app/assets/javascripts/products.js b/app/assets/javascripts/products.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/products.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/app/assets/javascripts/reviews.js b/app/assets/javascripts/reviews.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/reviews.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/app/assets/javascripts/sessions.js b/app/assets/javascripts/sessions.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/sessions.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/app/assets/stylesheets/_settings.scss b/app/assets/stylesheets/_settings.scss new file mode 100644 index 0000000000..b67ff00cdc --- /dev/null +++ b/app/assets/stylesheets/_settings.scss @@ -0,0 +1,863 @@ +// Foundation for Sites Settings +// ----------------------------- +// +// Table of Contents: +// +// 1. Global +// 2. Breakpoints +// 3. The Grid +// 4. Base Typography +// 5. Typography Helpers +// 6. Abide +// 7. Accordion +// 8. Accordion Menu +// 9. Badge +// 10. Breadcrumbs +// 11. Button +// 12. Button Group +// 13. Callout +// 14. Card +// 15. Close Button +// 16. Drilldown +// 17. Dropdown +// 18. Dropdown Menu +// 19. Flexbox Utilities +// 20. Forms +// 21. Label +// 22. Media Object +// 23. Menu +// 24. Meter +// 25. Off-canvas +// 26. Orbit +// 27. Pagination +// 28. Progress Bar +// 29. Prototype Arrow +// 30. Prototype Border-Box +// 31. Prototype Border-None +// 32. Prototype Bordered +// 33. Prototype Display +// 34. Prototype Font-Styling +// 35. Prototype List-Style-Type +// 36. Prototype Overflow +// 37. Prototype Position +// 38. Prototype Rounded +// 39. Prototype Separator +// 40. Prototype Shadow +// 41. Prototype Sizing +// 42. Prototype Spacing +// 43. Prototype Text-Decoration +// 44. Prototype Text-Transformation +// 45. Prototype Text-Utilities +// 46. Responsive Embed +// 47. Reveal +// 48. Slider +// 49. Switch +// 50. Table +// 51. Tabs +// 52. Thumbnail +// 53. Title Bar +// 54. Tooltip +// 55. Top Bar +// 56. Xy Grid + +@import 'util/util'; + +// 1. Global +// --------- + +$global-font-size: 100%; +$global-width: rem-calc(1200); +$global-lineheight: 1.5; +$foundation-palette: ( + primary: #1779ba, + secondary: #767676, + success: #3adb76, + warning: #ffae00, + alert: #cc4b37, +); +$light-gray: #e6e6e6; +$medium-gray: #cacaca; +$dark-gray: #8a8a8a; +$black: #0a0a0a; +$white: #fefefe; +$body-background: $white; +$body-font-color: $black; +$body-font-family: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif; +$body-antialiased: true; +$global-margin: 1rem; +$global-padding: 1rem; +$global-position: 1rem; +$global-weight-normal: normal; +$global-weight-bold: bold; +$global-radius: 0; +$global-menu-padding: 0.7rem 1rem; +$global-menu-nested-margin: 1rem; +$global-text-direction: ltr; +$global-flexbox: true; +$global-prototype-breakpoints: false; +$global-color-pick-contrast-tolerance: 0; +$print-transparent-backgrounds: true; + +@include add-foundation-colors; + +// 2. Breakpoints +// -------------- + +$breakpoints: ( + small: 0, + medium: 640px, + large: 1024px, + xlarge: 1200px, + xxlarge: 1440px, +); +$print-breakpoint: large; +$breakpoint-classes: (small medium large); + +// 3. The Grid +// ----------- + +$grid-row-width: $global-width; +$grid-column-count: 12; +$grid-column-gutter: ( + small: 20px, + medium: 30px, +); +$grid-column-align-edge: true; +$grid-column-alias: 'columns'; +$block-grid-max: 8; + +// 4. Base Typography +// ------------------ + +$header-font-family: $body-font-family; +$header-font-weight: $global-weight-normal; +$header-font-style: normal; +$font-family-monospace: Consolas, 'Liberation Mono', Courier, monospace; +$header-color: inherit; +$header-lineheight: 1.4; +$header-margin-bottom: 0.5rem; +$header-styles: ( + small: ( + 'h1': ('font-size': 24), + 'h2': ('font-size': 20), + 'h3': ('font-size': 19), + 'h4': ('font-size': 18), + 'h5': ('font-size': 17), + 'h6': ('font-size': 16), + ), + medium: ( + 'h1': ('font-size': 48), + 'h2': ('font-size': 40), + 'h3': ('font-size': 31), + 'h4': ('font-size': 25), + 'h5': ('font-size': 20), + 'h6': ('font-size': 16), + ), +); +$header-text-rendering: optimizeLegibility; +$small-font-size: 80%; +$header-small-font-color: $medium-gray; +$paragraph-lineheight: 1.6; +$paragraph-margin-bottom: 1rem; +$paragraph-text-rendering: optimizeLegibility; +$code-color: $black; +$code-font-family: $font-family-monospace; +$code-font-weight: $global-weight-normal; +$code-background: $light-gray; +$code-border: 1px solid $medium-gray; +$code-padding: rem-calc(2 5 1); +$anchor-color: $primary-color; +$anchor-color-hover: scale-color($anchor-color, $lightness: -14%); +$anchor-text-decoration: none; +$anchor-text-decoration-hover: none; +$hr-width: $global-width; +$hr-border: 1px solid $medium-gray; +$hr-margin: rem-calc(20) auto; +$list-lineheight: $paragraph-lineheight; +$list-margin-bottom: $paragraph-margin-bottom; +$list-style-type: disc; +$list-style-position: outside; +$list-side-margin: 1.25rem; +$list-nested-side-margin: 1.25rem; +$defnlist-margin-bottom: 1rem; +$defnlist-term-weight: $global-weight-bold; +$defnlist-term-margin-bottom: 0.3rem; +$blockquote-color: $dark-gray; +$blockquote-padding: rem-calc(9 20 0 19); +$blockquote-border: 1px solid $medium-gray; +$cite-font-size: rem-calc(13); +$cite-color: $dark-gray; +$cite-pseudo-content: '\2014 \0020'; +$keystroke-font: $font-family-monospace; +$keystroke-color: $black; +$keystroke-background: $light-gray; +$keystroke-padding: rem-calc(2 4 0); +$keystroke-radius: $global-radius; +$abbr-underline: 1px dotted $black; + +// 5. Typography Helpers +// --------------------- + +$lead-font-size: $global-font-size * 1.25; +$lead-lineheight: 1.6; +$subheader-lineheight: 1.4; +$subheader-color: $dark-gray; +$subheader-font-weight: $global-weight-normal; +$subheader-margin-top: 0.2rem; +$subheader-margin-bottom: 0.5rem; +$stat-font-size: 2.5rem; + +// 6. Abide +// -------- + +$abide-inputs: true; +$abide-labels: true; +$input-background-invalid: get-color(alert); +$form-label-color-invalid: get-color(alert); +$input-error-color: get-color(alert); +$input-error-font-size: rem-calc(12); +$input-error-font-weight: $global-weight-bold; + +// 7. Accordion +// ------------ + +$accordion-background: $white; +$accordion-plusminus: true; +$accordion-title-font-size: rem-calc(12); +$accordion-item-color: $primary-color; +$accordion-item-background-hover: $light-gray; +$accordion-item-padding: 1.25rem 1rem; +$accordion-content-background: $white; +$accordion-content-border: 1px solid $light-gray; +$accordion-content-color: $body-font-color; +$accordion-content-padding: 1rem; + +// 8. Accordion Menu +// ----------------- + +$accordionmenu-padding: $global-menu-padding; +$accordionmenu-nested-margin: $global-menu-nested-margin; +$accordionmenu-submenu-padding: $accordionmenu-padding; +$accordionmenu-arrows: true; +$accordionmenu-arrow-color: $primary-color; +$accordionmenu-item-background: null; +$accordionmenu-border: null; +$accordionmenu-submenu-toggle-background: null; +$accordion-submenu-toggle-border: $accordionmenu-border; +$accordionmenu-submenu-toggle-width: 40px; +$accordionmenu-submenu-toggle-height: $accordionmenu-submenu-toggle-width; +$accordionmenu-arrow-size: 6px; + +// 9. Badge +// -------- + +$badge-background: $primary-color; +$badge-color: $white; +$badge-color-alt: $black; +$badge-palette: $foundation-palette; +$badge-padding: 0.3em; +$badge-minwidth: 2.1em; +$badge-font-size: 0.6rem; + +// 10. Breadcrumbs +// --------------- + +$breadcrumbs-margin: 0 0 $global-margin 0; +$breadcrumbs-item-font-size: rem-calc(11); +$breadcrumbs-item-color: $primary-color; +$breadcrumbs-item-color-current: $black; +$breadcrumbs-item-color-disabled: $medium-gray; +$breadcrumbs-item-margin: 0.75rem; +$breadcrumbs-item-uppercase: true; +$breadcrumbs-item-separator: true; +$breadcrumbs-item-separator-item: '/'; +$breadcrumbs-item-separator-item-rtl: '\\'; +$breadcrumbs-item-separator-color: $medium-gray; + +// 11. Button +// ---------- + +$button-font-family: inherit; +$button-padding: 0.85em 1em; +$button-margin: 0 0 $global-margin 0; +$button-fill: solid; +$button-background: $primary-color; +$button-background-hover: scale-color($button-background, $lightness: -15%); +$button-color: $white; +$button-color-alt: $black; +$button-radius: $global-radius; +$button-hollow-border-width: 1px; +$button-sizes: ( + tiny: 0.6rem, + small: 0.75rem, + default: 0.9rem, + large: 1.25rem, +); +$button-palette: $foundation-palette; +$button-opacity-disabled: 0.25; +$button-background-hover-lightness: -20%; +$button-hollow-hover-lightness: -50%; +$button-transition: background-color 0.25s ease-out, color 0.25s ease-out; + +// 12. Button Group +// ---------------- + +$buttongroup-margin: 1rem; +$buttongroup-spacing: 1px; +$buttongroup-child-selector: '.button'; +$buttongroup-expand-max: 6; +$buttongroup-radius-on-each: true; + +// 13. Callout +// ----------- + +$callout-background: $white; +$callout-background-fade: 85%; +$callout-border: 1px solid rgba($black, 0.25); +$callout-margin: 0 0 1rem 0; +$callout-padding: 1rem; +$callout-font-color: $body-font-color; +$callout-font-color-alt: $body-background; +$callout-radius: $global-radius; +$callout-link-tint: 30%; + +// 14. Card +// -------- + +$card-background: $white; +$card-font-color: $body-font-color; +$card-divider-background: $light-gray; +$card-border: 1px solid $light-gray; +$card-shadow: none; +$card-border-radius: $global-radius; +$card-padding: $global-padding; +$card-margin-bottom: $global-margin; + +// 15. Close Button +// ---------------- + +$closebutton-position: right top; +$closebutton-offset-horizontal: ( + small: 0.66rem, + medium: 1rem, +); +$closebutton-offset-vertical: ( + small: 0.33em, + medium: 0.5rem, +); +$closebutton-size: ( + small: 1.5em, + medium: 2em, +); +$closebutton-lineheight: 1; +$closebutton-color: $dark-gray; +$closebutton-color-hover: $black; + +// 16. Drilldown +// ------------- + +$drilldown-transition: transform 0.15s linear; +$drilldown-arrows: true; +$drilldown-padding: $global-menu-padding; +$drilldown-nested-margin: 0; +$drilldown-background: $white; +$drilldown-submenu-padding: $drilldown-padding; +$drilldown-submenu-background: $white; +$drilldown-arrow-color: $primary-color; +$drilldown-arrow-size: 6px; + +// 17. Dropdown +// ------------ + +$dropdown-padding: 1rem; +$dropdown-background: $body-background; +$dropdown-border: 1px solid $medium-gray; +$dropdown-font-size: 1rem; +$dropdown-width: 300px; +$dropdown-radius: $global-radius; +$dropdown-sizes: ( + tiny: 100px, + small: 200px, + large: 400px, +); + +// 18. Dropdown Menu +// ----------------- + +$dropdownmenu-arrows: true; +$dropdownmenu-arrow-color: $anchor-color; +$dropdownmenu-arrow-size: 6px; +$dropdownmenu-arrow-padding: 1.5rem; +$dropdownmenu-min-width: 200px; +$dropdownmenu-background: $white; +$dropdownmenu-submenu-background: $dropdownmenu-background; +$dropdownmenu-padding: $global-menu-padding; +$dropdownmenu-nested-margin: 0; +$dropdownmenu-submenu-padding: $dropdownmenu-padding; +$dropdownmenu-border: 1px solid $medium-gray; +$dropdown-menu-item-color-active: get-color(primary); +$dropdown-menu-item-background-active: transparent; + +// 19. Flexbox Utilities +// --------------------- + +$flex-source-ordering-count: 6; +$flexbox-responsive-breakpoints: true; + +// 20. Forms +// --------- + +$fieldset-border: 1px solid $medium-gray; +$fieldset-padding: rem-calc(20); +$fieldset-margin: rem-calc(18 0); +$legend-padding: rem-calc(0 3); +$form-spacing: rem-calc(16); +$helptext-color: $black; +$helptext-font-size: rem-calc(13); +$helptext-font-style: italic; +$input-prefix-color: $black; +$input-prefix-background: $light-gray; +$input-prefix-border: 1px solid $medium-gray; +$input-prefix-padding: 1rem; +$form-label-color: $black; +$form-label-font-size: rem-calc(14); +$form-label-font-weight: $global-weight-normal; +$form-label-line-height: 1.8; +$select-background: $white; +$select-triangle-color: $dark-gray; +$select-radius: $global-radius; +$input-color: $black; +$input-placeholder-color: $medium-gray; +$input-font-family: inherit; +$input-font-size: rem-calc(16); +$input-font-weight: $global-weight-normal; +$input-line-height: $global-lineheight; +$input-background: $white; +$input-background-focus: $white; +$input-background-disabled: $light-gray; +$input-border: 1px solid $medium-gray; +$input-border-focus: 1px solid $dark-gray; +$input-padding: $form-spacing / 2; +$input-shadow: inset 0 1px 2px rgba($black, 0.1); +$input-shadow-focus: 0 0 5px $medium-gray; +$input-cursor-disabled: not-allowed; +$input-transition: box-shadow 0.5s, border-color 0.25s ease-in-out; +$input-number-spinners: true; +$input-radius: $global-radius; +$form-button-radius: $global-radius; + +// 21. Label +// --------- + +$label-background: $primary-color; +$label-color: $white; +$label-color-alt: $black; +$label-palette: $foundation-palette; +$label-font-size: 0.8rem; +$label-padding: 0.33333rem 0.5rem; +$label-radius: $global-radius; + +// 22. Media Object +// ---------------- + +$mediaobject-margin-bottom: $global-margin; +$mediaobject-section-padding: $global-padding; +$mediaobject-image-width-stacked: 100%; + +// 23. Menu +// -------- + +$menu-margin: 0; +$menu-nested-margin: $global-menu-nested-margin; +$menu-items-padding: $global-menu-padding; +$menu-simple-margin: 1rem; +$menu-item-color-active: $white; +$menu-item-background-active: get-color(primary); +$menu-icon-spacing: 0.25rem; +$menu-item-background-hover: $light-gray; +$menu-state-back-compat: true; +$menu-centered-back-compat: true; + +// 24. Meter +// --------- + +$meter-height: 1rem; +$meter-radius: $global-radius; +$meter-background: $medium-gray; +$meter-fill-good: $success-color; +$meter-fill-medium: $warning-color; +$meter-fill-bad: $alert-color; + +// 25. Off-canvas +// -------------- + +$offcanvas-size: 250px; +$offcanvas-vertical-size: 250px; +$offcanvas-background: $light-gray; +$offcanvas-shadow: 0 0 10px rgba($black, 0.7); +$offcanvas-inner-shadow-size: 20px; +$offcanvas-inner-shadow-color: rgba($black, 0.25); +$offcanvas-overlay-zindex: 11; +$offcanvas-push-zindex: 12; +$offcanvas-overlap-zindex: 13; +$offcanvas-reveal-zindex: 12; +$offcanvas-transition-length: 0.5s; +$offcanvas-transition-timing: ease; +$offcanvas-fixed-reveal: true; +$offcanvas-exit-background: rgba($white, 0.25); +$maincontent-class: 'off-canvas-content'; + +// 26. Orbit +// --------- + +$orbit-bullet-background: $medium-gray; +$orbit-bullet-background-active: $dark-gray; +$orbit-bullet-diameter: 1.2rem; +$orbit-bullet-margin: 0.1rem; +$orbit-bullet-margin-top: 0.8rem; +$orbit-bullet-margin-bottom: 0.8rem; +$orbit-caption-background: rgba($black, 0.5); +$orbit-caption-padding: 1rem; +$orbit-control-background-hover: rgba($black, 0.5); +$orbit-control-padding: 1rem; +$orbit-control-zindex: 10; + +// 27. Pagination +// -------------- + +$pagination-font-size: rem-calc(14); +$pagination-margin-bottom: $global-margin; +$pagination-item-color: $black; +$pagination-item-padding: rem-calc(3 10); +$pagination-item-spacing: rem-calc(1); +$pagination-radius: $global-radius; +$pagination-item-background-hover: $light-gray; +$pagination-item-background-current: $primary-color; +$pagination-item-color-current: $white; +$pagination-item-color-disabled: $medium-gray; +$pagination-ellipsis-color: $black; +$pagination-mobile-items: false; +$pagination-mobile-current-item: false; +$pagination-arrows: true; + +// 28. Progress Bar +// ---------------- + +$progress-height: 1rem; +$progress-background: $medium-gray; +$progress-margin-bottom: $global-margin; +$progress-meter-background: $primary-color; +$progress-radius: $global-radius; + +// 29. Prototype Arrow +// ------------------- + +$prototype-arrow-directions: ( + down, + up, + right, + left +); +$prototype-arrow-size: 0.4375rem; +$prototype-arrow-color: $black; + +// 30. Prototype Border-Box +// ------------------------ + +$prototype-border-box-breakpoints: $global-prototype-breakpoints; + +// 31. Prototype Border-None +// ------------------------- + +$prototype-border-none-breakpoints: $global-prototype-breakpoints; + +// 32. Prototype Bordered +// ---------------------- + +$prototype-bordered-breakpoints: $global-prototype-breakpoints; +$prototype-border-width: rem-calc(1); +$prototype-border-type: solid; +$prototype-border-color: $medium-gray; + +// 33. Prototype Display +// --------------------- + +$prototype-display-breakpoints: $global-prototype-breakpoints; +$prototype-display: ( + inline, + inline-block, + block, + table, + table-cell +); + +// 34. Prototype Font-Styling +// -------------------------- + +$prototype-font-breakpoints: $global-prototype-breakpoints; +$prototype-wide-letter-spacing: rem-calc(4); +$prototype-font-normal: $global-weight-normal; +$prototype-font-bold: $global-weight-bold; + +// 35. Prototype List-Style-Type +// ----------------------------- + +$prototype-list-breakpoints: $global-prototype-breakpoints; +$prototype-style-type-unordered: ( + disc, + circle, + square +); +$prototype-style-type-ordered: ( + decimal, + lower-alpha, + lower-latin, + lower-roman, + upper-alpha, + upper-latin, + upper-roman +); + +// 36. Prototype Overflow +// ---------------------- + +$prototype-overflow-breakpoints: $global-prototype-breakpoints; +$prototype-overflow: ( + visible, + hidden, + scroll +); + +// 37. Prototype Position +// ---------------------- + +$prototype-position-breakpoints: $global-prototype-breakpoints; +$prototype-position: ( + static, + relative, + absolute, + fixed +); +$prototype-position-z-index: 975; + +// 38. Prototype Rounded +// --------------------- + +$prototype-rounded-breakpoints: $global-prototype-breakpoints; +$prototype-border-radius: rem-calc(3); + +// 39. Prototype Separator +// ----------------------- + +$prototype-separator-breakpoints: $global-prototype-breakpoints; +$prototype-separator-align: center; +$prototype-separator-height: rem-calc(2); +$prototype-separator-width: 3rem; +$prototype-separator-background: $primary-color; +$prototype-separator-margin-top: $global-margin; + +// 40. Prototype Shadow +// -------------------- + +$prototype-shadow-breakpoints: $global-prototype-breakpoints; +$prototype-box-shadow: 0 2px 5px 0 rgba(0,0,0,.16), + 0 2px 10px 0 rgba(0,0,0,.12); + +// 41. Prototype Sizing +// -------------------- + +$prototype-sizing-breakpoints: $global-prototype-breakpoints; +$prototype-sizing: ( + width, + height +); +$prototype-sizes: ( + 25: 25%, + 50: 50%, + 75: 75%, + 100: 100% +); + +// 42. Prototype Spacing +// --------------------- + +$prototype-spacing-breakpoints: $global-prototype-breakpoints; +$prototype-spacers-count: 3; + +// 43. Prototype Text-Decoration +// ----------------------------- + +$prototype-decoration-breakpoints: $global-prototype-breakpoints; +$prototype-text-decoration: ( + overline, + underline, + line-through, +); + +// 44. Prototype Text-Transformation +// --------------------------------- + +$prototype-transformation-breakpoints: $global-prototype-breakpoints; +$prototype-text-transformation: ( + lowercase, + uppercase, + capitalize +); + +// 45. Prototype Text-Utilities +// ---------------------------- + +$prototype-utilities-breakpoints: $global-prototype-breakpoints; +$prototype-text-overflow: ellipsis; + +// 46. Responsive Embed +// -------------------- + +$responsive-embed-margin-bottom: rem-calc(16); +$responsive-embed-ratios: ( + default: 4 by 3, + widescreen: 16 by 9, +); + +// 47. Reveal +// ---------- + +$reveal-background: $white; +$reveal-width: 600px; +$reveal-max-width: $global-width; +$reveal-padding: $global-padding; +$reveal-border: 1px solid $medium-gray; +$reveal-radius: $global-radius; +$reveal-zindex: 1005; +$reveal-overlay-background: rgba($black, 0.45); + +// 48. Slider +// ---------- + +$slider-width-vertical: 0.5rem; +$slider-transition: all 0.2s ease-in-out; +$slider-height: 0.5rem; +$slider-background: $light-gray; +$slider-fill-background: $medium-gray; +$slider-handle-height: 1.4rem; +$slider-handle-width: 1.4rem; +$slider-handle-background: $primary-color; +$slider-opacity-disabled: 0.25; +$slider-radius: $global-radius; + +// 49. Switch +// ---------- + +$switch-background: $medium-gray; +$switch-background-active: $primary-color; +$switch-height: 2rem; +$switch-height-tiny: 1.5rem; +$switch-height-small: 1.75rem; +$switch-height-large: 2.5rem; +$switch-radius: $global-radius; +$switch-margin: $global-margin; +$switch-paddle-background: $white; +$switch-paddle-offset: 0.25rem; +$switch-paddle-radius: $global-radius; +$switch-paddle-transition: all 0.25s ease-out; + +// 50. Table +// --------- + +$table-background: $white; +$table-color-scale: 5%; +$table-border: 1px solid smart-scale($table-background, $table-color-scale); +$table-padding: rem-calc(8 10 10); +$table-hover-scale: 2%; +$table-row-hover: darken($table-background, $table-hover-scale); +$table-row-stripe-hover: darken($table-background, $table-color-scale + $table-hover-scale); +$table-is-striped: true; +$table-striped-background: smart-scale($table-background, $table-color-scale); +$table-stripe: even; +$table-head-background: smart-scale($table-background, $table-color-scale / 2); +$table-head-row-hover: darken($table-head-background, $table-hover-scale); +$table-foot-background: smart-scale($table-background, $table-color-scale); +$table-foot-row-hover: darken($table-foot-background, $table-hover-scale); +$table-head-font-color: $body-font-color; +$table-foot-font-color: $body-font-color; +$show-header-for-stacked: false; +$table-stack-breakpoint: medium; + +// 51. Tabs +// -------- + +$tab-margin: 0; +$tab-background: $white; +$tab-color: $primary-color; +$tab-background-active: $light-gray; +$tab-active-color: $primary-color; +$tab-item-font-size: rem-calc(12); +$tab-item-background-hover: $white; +$tab-item-padding: 1.25rem 1.5rem; +$tab-expand-max: 6; +$tab-content-background: $white; +$tab-content-border: $light-gray; +$tab-content-color: $body-font-color; +$tab-content-padding: 1rem; + +// 52. Thumbnail +// ------------- + +$thumbnail-border: solid 4px $white; +$thumbnail-margin-bottom: $global-margin; +$thumbnail-shadow: 0 0 0 1px rgba($black, 0.2); +$thumbnail-shadow-hover: 0 0 6px 1px rgba($primary-color, 0.5); +$thumbnail-transition: box-shadow 200ms ease-out; +$thumbnail-radius: $global-radius; + +// 53. Title Bar +// ------------- + +$titlebar-background: $black; +$titlebar-color: $white; +$titlebar-padding: 0.5rem; +$titlebar-text-font-weight: bold; +$titlebar-icon-color: $white; +$titlebar-icon-color-hover: $medium-gray; +$titlebar-icon-spacing: 0.25rem; + +// 54. Tooltip +// ----------- + +$has-tip-cursor: help; +$has-tip-font-weight: $global-weight-bold; +$has-tip-border-bottom: dotted 1px $dark-gray; +$tooltip-background-color: $black; +$tooltip-color: $white; +$tooltip-padding: 0.75rem; +$tooltip-max-width: 10rem; +$tooltip-font-size: $small-font-size; +$tooltip-pip-width: 0.75rem; +$tooltip-pip-height: $tooltip-pip-width * 0.866; +$tooltip-radius: $global-radius; + +// 55. Top Bar +// ----------- + +$topbar-padding: 0.5rem; +$topbar-background: $light-gray; +$topbar-submenu-background: $topbar-background; +$topbar-title-spacing: 0.5rem 1rem 0.5rem 0; +$topbar-input-width: 200px; +$topbar-unstack-breakpoint: medium; + +// 56. Xy Grid +// ----------- + +$xy-grid: true; +$grid-container: $global-width; +$grid-columns: 12; +$grid-margin-gutters: ( + small: 20px, + medium: 30px +); +$grid-padding-gutters: $grid-margin-gutters; +$grid-container-padding: $grid-padding-gutters; +$grid-container-max: $global-width; +$block-grid-max: 8; + diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css new file mode 100644 index 0000000000..ed6a2db3f8 --- /dev/null +++ b/app/assets/stylesheets/application.css @@ -0,0 +1,334 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's + * vendor/assets/stylesheets directory can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS + * files in this directory. Styles in this file should be added after the last require_* statement. + * It is generally better to create a new file per style scope. + * + *= require_tree . + *= require foundation_and_overrides + *= require_self + + */ + + +/* FLASH NOTIFICATIONS */ + +/** { + border: 1px solid black; +}*/ + +ul { + list-style-type: none; +} + +body{ + color: #2E4434; + position: relative; + background-color: #f3fbef; +} + +body, h1, h2, h3, h4, h5, h6, div, article, section, p, span, .button { + font-family: 'Roboto', sans-serif; +} + +a { + color: #426421; +} + +a:hover { + color: #2E4434; +} + +select { + border-radius: 10px; + height: 40px; + -webkit-appearance: none; +} + +.mini-heading a { + color: black; + text-shadow: 1px 2px #7Da50f; +} + +.mini-heading a:hover{ + color: #006400; +} + +.mini-heading{ + color: black; + text-shadow: 1px 2px #7Da50f; +} + +#nav-container { + margin-top: 0; + padding-top: 10px; + background-color: #86967D; + min-width: 100%; + box-shadow: 0 2px 2px gray; +} + +.nav-bar { + color: #F3FBEF; + padding-left: 5%; + padding-right: 5%; +} + +.dark-accent { + color: #2E4434; +} + +.nav-bar small { + color: #F3FBEF; + font-size: 0.6em; +} + +.nav-bar a { + color: #F3FBEF; +} + +.nav-bar a:hover { + color: #426421; +} + +.nav-bar h3 { + margin-bottom: 0; + margin-top: 10px; +} + +@media (max-width: 639px) { + .logo { + margin-top: 14px; + } +} + +@media (max-width: 1023px) { + .hide-small { + display: none; + } +} + +.inline-list li { + display: inline-block; + vertical-align: bottom; +} + +.row { + width: 100%; +} + +.page-header { + margin-bottom: 30px; + margin-top: 10px; + max-width: 95%; + left: 0; + top: 0; +} + +.bg { + background-color: brown; + opacity: 0.4; +} + +.content { + width: 100% +} + +.full-width { + width: 100%; + max-width: 95%; + margin-left: auto; + margin-right: auto; +} + +.half-width { + width: 100%; + max-width: 60%; + margin-left: auto; + margin-right: auto; +} + +.filter { + width: 80%; +} + +.nomar { + margin: 0; +} + +.filter .button { + position: relative; + top: 22px; +} + +.review .button { + position: relative; + top: 8px; +} + +.flash { + border: 1px solid; + border-radius: 3px; + margin: auto; + margin-bottom: 10px; + max-width: 80%; + min-height: 50px; + font-size: 1.1rem; + display: none; +} + +.success { + background-color: #DFF0D8; + background-color: #D1EDC0; + border-color: #D9ECCC; + color: #4C7D45; + display: block; +} + +.error { + background-color: #F2DEDE; + border-color: #DDB5B5; + color: #AB4B4A; + color: #D38865; + background-color: #EFD2C6; + display: block; +} + +.flash p { + margin-left: 10px; + margin-bottom: 0px; + line-height: 50px; +} + +.flash .error-messages { + margin-left: 50px; + padding-bottom: 10px; +} + +.float-right { + float: right; +} + +.dropdown { + position: relative; + display: inline-block; +} + +.dropdown-content { + display: none; + font-size: 0.4em; + position: absolute; + background-color: #86967D; + min-width: 170px; + padding: 12px 16px; + z-index: 1; +} + +.dropdown:hover .dropdown-content { + display: block; +} + +.cart { + padding-left: 10px; +} + +.space { + display: inline-block; + width: 0.385em; +} + + +.button { +color: #F3FBEF; +background-color: #426421; +border-radius: 8px; + } + +.button:hover { + background-color: #2E4434; +} + +.button:focus { + background-color: #2E4434; +} + +/***********star ratings for reviews********************/ +.dynamic-star { + color: #fcfcfc; + display: inline-block; + height: 25px; + line-height: 25px; + margin: 0 auto; + padding: 0; + position: relative; + text-shadow: 0 1px 1.5px gray; + unicode-bidi: bidi-override; + width: 80px; +} + +.overlay { + color: black; + padding: 0; + position: absolute; + overflow: hidden; + top: 0; + left: 0; + white-space: nowrap; + z-index: 1; +} + +.underlay { + color: #fcfcfc; + display: block; + padding: 0; + z-index: 0; +} + +.star { + float: left; + text-shadow: 0 1px 1.5px gray; +} + +.checked { + color: black; +} + +.unchecked { + color: #fcfcfc; +} + +.rating-labels { + display: inline-block; + transform: rotateY(180deg); +} + +.rating-labels input[type="radio"] { + display: none; +} +.rating-labels input[type="radio"]:checked ~ label::before { + color: #2E4434; +} + +.rating-labels label { + display: inline-block; +} + +.rating-labels label::before { + color: white; + content: "\2605"; + text-shadow: 0 1px 1.5px #2E4434; +} + +.rating-labels label:hover::before { + color: #86967D; +} + +.rating-labels label:hover ~ label::before { + color: #86967D; + +} diff --git a/app/assets/stylesheets/foundation_and_overrides.scss b/app/assets/stylesheets/foundation_and_overrides.scss new file mode 100644 index 0000000000..584bc3205b --- /dev/null +++ b/app/assets/stylesheets/foundation_and_overrides.scss @@ -0,0 +1,53 @@ +@charset 'utf-8'; + +@import 'settings'; +@import 'foundation'; + +// If you'd like to include motion-ui the foundation-rails gem comes prepackaged with it, uncomment the 3 @imports, if you are not using the gem you need to install the motion-ui sass package. +// +@import 'motion-ui/motion-ui'; + +// We include everything by default. To slim your CSS, remove components you don't use. + +@include foundation-global-styles; +@include foundation-grid; +@include foundation-typography; +@include foundation-button; +@include foundation-forms; +@include foundation-visibility-classes; +@include foundation-float-classes; +@include foundation-accordion; +@include foundation-accordion-menu; +@include foundation-badge; +@include foundation-breadcrumbs; +@include foundation-button-group; +@include foundation-callout; +@include foundation-card; +@include foundation-close-button; +@include foundation-drilldown-menu; +@include foundation-dropdown; +@include foundation-dropdown-menu; +@include foundation-responsive-embed; +@include foundation-label; +@include foundation-media-object; +@include foundation-menu; +@include foundation-menu-icon; +@include foundation-off-canvas; +@include foundation-orbit; +@include foundation-pagination; +@include foundation-progress-bar; +@include foundation-slider; +@include foundation-sticky; +@include foundation-reveal; +@include foundation-switch; +@include foundation-table; +@include foundation-tabs; +@include foundation-thumbnail; +@include foundation-title-bar; +@include foundation-tooltip; +@include foundation-top-bar; + +// If you'd like to include motion-ui the foundation-rails gem comes prepackaged with it, uncomment the 3 @imports, if you are not using the gem you need to install the motion-ui sass package. + +@include motion-ui-transitions; +@include motion-ui-animations; diff --git a/app/assets/stylesheets/homepage.scss b/app/assets/stylesheets/homepage.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/assets/stylesheets/merchants.scss b/app/assets/stylesheets/merchants.scss new file mode 100644 index 0000000000..2327c00afe --- /dev/null +++ b/app/assets/stylesheets/merchants.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the merchants controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/orders.scss b/app/assets/stylesheets/orders.scss new file mode 100644 index 0000000000..3b0428a94e --- /dev/null +++ b/app/assets/stylesheets/orders.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the orders controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/products.scss b/app/assets/stylesheets/products.scss new file mode 100644 index 0000000000..37cf680cb3 --- /dev/null +++ b/app/assets/stylesheets/products.scss @@ -0,0 +1,83 @@ +// Place all the styles related to the products controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ + +//INDEX////////////////////////////////////////////////////////// +.product-card { + box-shadow: 1px 1px 3px 3px rgba(0,0,0,0.2); + height: 350px; + border-radius: 10px; + background-color: white; + margin: 4.15%; + position: relative; + figure { + height: 180px + } + img { + display: block; + max-width: 100%; + max-height: 90%; + overflow: hidden; + margin-top: 15px; + margin-left: auto; + margin-right: auto; + margin-bottom: 30px; + } + .button { + display: block; + margin-left: 25%; + margin-right: 25%; + } +} + +.column:last-child:not(:first-child), .columns:last-child:not(:first-child) { + float: left; +} + +.product-card-details { + height: 150px; + overflow: hidden; +} + +.page { + * { + float: left; + } + em.current { + background-color: #426421; + } +} + +//SHOW////////////////////////////////////////////////////////// +.product-show { + figure { + height: 400px; + img { + max-height: 350px; + } + } + article { + // padding-left: 3%; + padding: 0px; + h2 { + // margin-right: 5%; + display: inline-block; + } + } +} + +//LEAVE A REVIEW BUTTON///////////// +.reviews { + a.button { + margin-left: -7px; + margin-top: 5px; + margin-bottom: 50px; + } + section.bottom-space { + margin-bottom: 20px; + } + li.smaller { + font-size: 0.9rem; + margin-bottom: 2px; + } +} diff --git a/app/assets/stylesheets/reviews.scss b/app/assets/stylesheets/reviews.scss new file mode 100644 index 0000000000..6ea2454d26 --- /dev/null +++ b/app/assets/stylesheets/reviews.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the reviews controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/sessions.scss b/app/assets/stylesheets/sessions.scss new file mode 100644 index 0000000000..7bef9cf826 --- /dev/null +++ b/app/assets/stylesheets/sessions.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the sessions controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/channels/application_cable/channel.rb b/app/channels/application_cable/channel.rb new file mode 100644 index 0000000000..d672697283 --- /dev/null +++ b/app/channels/application_cable/channel.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Channel < ActionCable::Channel::Base + end +end diff --git a/app/channels/application_cable/connection.rb b/app/channels/application_cable/connection.rb new file mode 100644 index 0000000000..0ff5442f47 --- /dev/null +++ b/app/channels/application_cable/connection.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Connection < ActionCable::Connection::Base + end +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb new file mode 100644 index 0000000000..730de7ea4a --- /dev/null +++ b/app/controllers/application_controller.rb @@ -0,0 +1,22 @@ +class ApplicationController < ActionController::Base + protect_from_forgery with: :exception + + before_action :assign_order + + def render_404 + render file: "/public/404.html", status: 404 + end + + private + def assign_order + if session[:order_id] == nil || Order.find_by(id: session[:order_id], status: "pending") == nil + order = Order.new(status: "pending") + if order.save + session[:order_id] = order.id + else + redirect_to root_path, status: 500 + end + end + end + +end diff --git a/app/controllers/concerns/.keep b/app/controllers/concerns/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/controllers/merchants_controller.rb b/app/controllers/merchants_controller.rb new file mode 100644 index 0000000000..516b1b3c6e --- /dev/null +++ b/app/controllers/merchants_controller.rb @@ -0,0 +1,114 @@ +class MerchantsController < ApplicationController + before_action :find_merchant, only: [:show, :edit, :update, :destroy, :fulfillment, :show_order, :ship_orderitems] + before_action :account_owner?, only: [:show, :edit, :update, :destroy, :fulfillment, :show_order, :ship_orderitems] + + def new + end + + def show_order + @order = Order.find_by(id: params[:order_id]) + if @order == nil + flash[:status] = :error + flash[:result_text] = "Invalid order" + return redirect_back(fallback_location: root_path) + end + @title = "Review Order #{@order.id}" + @orderitems = @order.orderitems.select { |item| item.product.merchant_id == @merchant.id } + @unshipped = (@orderitems.select { |item| item.status != "shipped"}.length >0 ) + end + + def fulfillment + if params[:id].to_i == session[:merchant_id] + status = params[:status] + orderitems = @merchant.join_orderitems(status) + @orders = {} + orderitems.each do |orderitem| + if @orders[orderitem.order_id.to_s] == nil + new_revenue = orderitem.quantity * orderitem.product.price + new_items = [orderitem] + else + new_revenue = @orders[orderitem.order_id.to_s][:revenue] + (orderitem.quantity * orderitem.product.price) + new_items = @orders[orderitem.order_id.to_s][:items] + new_items << orderitem + end + @orders[orderitem.order_id.to_s] = {status: orderitem.order.status, purchase_date: orderitem.order.purchase_datetime,revenue: new_revenue, items: new_items, unshipped: ""} + end + @orders.each do |order_id, hash| + unshipped = hash[:items].select { |item| item.status != "shipped"}.length > 0 + @orders[order_id][:unshipped] = unshipped + end + @total_revenue = orderitems.sum {|orderitem| ( (orderitem.order.status == "cancelled") ? 0 : (orderitem.quantity * orderitem.product.price) ) } + @statuses = ["paid", "complete", "cancelled"] + @revenue_by_status = {} + @statuses.each do |a_status| + @revenue_by_status[a_status] = orderitems.sum {|orderitem| orderitem.order.status == a_status ? (orderitem.quantity * orderitem.product.price) : 0} + end + @title = "Fulfillment" + else + flash[:status] = :error + flash[:result_text] = "Please log in first" + redirect_to root_path + end + end + + def ship_orderitems + order = Order.find_by(id: params[:order_id].to_i) + orderitems = order.orderitems.select { |item| item.product.merchant_id == @merchant.id } + if orderitems.length < 1 + flash[:status] = :error + flash[:result_text] = "Invalid order" + return redirect_back(fallback_location: root_path) + end + saveditems = 0 + orderitems.each do |item| + item.status = "shipped" + saveditems +=1 if item.save + end + order.status_check + if saveditems == orderitems.length + flash[:status] = :success + flash[:result_text] = "Order marked as shipped" + return redirect_back(fallback_location: merchant_fulfillment_path(@merchant.id)) + else + flash[:status] = :error + flash[:result_text] = "Order could not be marked as shipped" + redirect_back(fallback_location: root_path) + end + end + + def create + end + + def index + end + + def edit + end + + def update + end + + def show + @title = "My Account" + end + + private + + def find_merchant + @merchant = Merchant.find_by_id(params[:id]) + unless @merchant + flash[:status] = :error + flash[:result_text] = "Invalid request" + return redirect_back fallback_location: root_path + end + end + + def account_owner? + if session[:merchant_id] != @merchant.id + flash[:status] = :error + flash[:result_text] = "Unauthorized user" + return redirect_back fallback_location: root_path + end + end + +end diff --git a/app/controllers/orders_controller.rb b/app/controllers/orders_controller.rb new file mode 100644 index 0000000000..ca2a284545 --- /dev/null +++ b/app/controllers/orders_controller.rb @@ -0,0 +1,186 @@ +class OrdersController < ApplicationController + before_action :find_orderitem, only: [:add_item, :update_quantity, :remove_from_cart] + before_action :find_cart, only: [:show_cart, :update_quantity, :remove_from_cart, :checkout] + after_action :assign_order, only: [:checkout] + before_action :find_order, only: [:confirmation] + + def index + end + + def create + end + + def checkout_form + @title = "Checkout" + end + + def checkout + #verify everything is in stock + @cart.orderitems.each do |orderitem| + if orderitem.product.quantity == 0 + flash[:status] = :error + flash[:result_text] = "#{orderitem.product.name} is out of stock" + return redirect_to show_cart_path + elsif orderitem.quantity > orderitem.product.quantity + flash[:status] = :error + flash[:result_text] = "You attempted to purchase #{orderitem.quantity} #{orderitem.product.name}, but there are only #{orderitem.product.quantity} available." + return redirect_to show_cart_path + end + end + #validate user input + + #modify the cart + @cart.status = "paid" + @cart.purchase_datetime = DateTime.now + puts @cart.purchase_datetime.to_date + + @cart.update_attributes(checkout_params) + + #checkout the cart + if @cart.save + @cart.orderitems.each do |orderitem| + product = orderitem.product + product.quantity -= orderitem.quantity + product.save + end + + session[:order_id] = nil + flash[:status] = :success + flash[:result_text] = "Your order has been placed" + return redirect_to order_confirmation_path(@cart.id) + else + flash.now[:status] = :error + flash.now[:result_text] = "Your order could not be placed at this time" + flash.now[:messages] = @cart.errors.messages + render :checkout_form + end + + end #checkout + + def confirmation + @total = @order.orderitems.sum { |orderitem| (orderitem.quantity * orderitem.product.price) } + @title = "Thank You" + end + + def new + end + + def edit + end + + def update + end + + def show + end + + def show_cart + @title = "My Cart" + end + + def add_item + product = Product.find_by(id: params[:product_id]) + if product + if product.quantity >= 1 + if @order_item + @order_item.quantity += 1 + else + @order_item = Orderitem.new(order_id: session[:order_id], product_id: params[:product_id], quantity: 1) + end + if @order_item.save + flash[:status] = :success + flash[:result_text] = "1 #{product.name} added to your cart" + redirect_back(fallback_location: root_path) + else + flash[:status] = :error + flash[:result_text] = "Unable to add #{product.name} to your cart" + redirect_back(fallback_location: root_path) + end + else + flash[:status] = :error + flash[:result_text] = "#{product.name} is out of stock" + redirect_back(fallback_location: root_path) + end + else + flash[:status] = :error + flash[:result_text] = "That is not a valid product" + redirect_back(fallback_location: root_path) + end + end + + def update_quantity + if @order_item + if @order_item.quantity == params[:quantity].to_i + flash[:status] = :success + flash[:result_text] = "#{@order_item.product.name} quantity is still #{@order_item.quantity}" + return redirect_to show_cart_path + end + if params[:quantity].to_i <= @order_item.product.quantity + @order_item.quantity = params[:quantity] + if @order_item.save + flash.now[:status] = :success + flash.now[:result_text] = "#{@order_item.product.name} quantity changed to #{params[:quantity]}" + return render :show_cart + else + flash.now[:status] = :error + flash.now[:result_text] = "#{@order_item.product.name} quantity was not changed" + return render :show_cart + end + else + flash.now[:status] = :error + flash.now[:result_text] = "You can only order up to #{@order_item.product.quantity} of #{@order_item.product.name}" + return render :show_cart + end + else + flash[:status] = :error + flash[:result_text] = "This item is not in your cart" + return redirect_back(fallback_location: root_path) + end + end + + def remove_from_cart + if @cart + if @order_item + if @order_item.destroy + flash[:status] = :success + flash[:result_text] = "#{@order_item.product.name} was removed from your cart" + redirect_back(fallback_location: show_cart_path) + else + flash[:status] = :error + flash[:result_text] = "#{@order_item.product.name} was not removed from your cart" + redirect_back(fallback_location: show_cart_path) + end + else + flash[:status] = :error + flash[:result_text] = "This item is not in your cart" + return redirect_back(fallback_location: show_cart_path) + end + else + flash[:status] = :error + flash[:result_text] = "Invalid shopping cart" + return redirect_back(fallback_location: show_cart_path) + end + end + + private + def find_orderitem + @order_item = Orderitem.find_by(order_id: session[:order_id], product_id: params[:product_id]) + end + + def find_cart + @cart = Order.find_by(id: session[:order_id], status: "pending") + end + + def find_order + @order = Order.find_by(id: params[:id]) + if @order == nil + flash[:status] = :error + flash[:result_text] = "Invalid order" + redirect_back(fallback_location: root_path) + end + end + + def checkout_params + params.permit(:customer_name, :customer_email, :address1, :address2, :city, :state, :zipcode, :cc_name, :cc_number, :cc_expiration, :cc_security, :billingzip) + end +end diff --git a/app/controllers/products_controller.rb b/app/controllers/products_controller.rb new file mode 100644 index 0000000000..a73e231fd6 --- /dev/null +++ b/app/controllers/products_controller.rb @@ -0,0 +1,144 @@ +class ProductsController < ApplicationController +before_action :find_product, only: [:show, :edit, :update, :retire] +before_action :find_merchant, except: [:index, :destroy] +before_action :get_categories, only: [:index, :edit, :new] + + def index + + unless @merchant + merchant = params[:merchant] + end + + unless @category + category = params[:category] + end + + @products = (Product.get_products(a_category: category, a_merchant: merchant) & Product.active_only).paginate(page: params[:page], per_page: 15) + + # IF NEEDED UNCOMMENT self.retired_only IN MODEL + # @retired_products = Product.get_products(a_category: params[:category], a_merchant: params[:merchant]) & Product.retired_only + + # UNCOMMENT IF NEEDED + # @all_products = Product.get_products(a_category: params[:category], a_merchant: params[:merchant]) + end + + def show + unless @merchant == @product.merchant + render_404 if @product.status == "retired" + end + @title = @product.name + end + + def edit + unless @merchant == @product.merchant + flash[:status] = :error + flash[:result_text] = "Unauthorized user" + return redirect_to root_path + end + @title = "Edit #{@product.name}" + end + + def update + unless @merchant == @product.merchant + flash[:status] = :error + flash[:result_text] = "Unauthorized user" + return redirect_to root_path + end + @product.update_attributes(product_params) + update_categories + + if @product.save + flash[:status] = :success + flash[:result_text] = "#{@product.name} updated." + redirect_to product_path(@product.id) + else + @categories = @product.categories + flash.now[:status] = :error + flash.now[:result_text] = "Could not update product" + flash.now[:messages] = @product.errors.messages + render :edit, status: 400 + end + end + + def new + unless @merchant + flash[:status] = :error + flash[:result_text] = "You need to be logged in to create a product!" + redirect_back fallback_location: root_path + end + + @product = Product.new(merchant_id: session[:merchant_id]) + @title = "New Product" + end + + def create + unless @merchant + flash[:status] = :error + flash[:result_text] = "You need to be logged in to create a product!" + return redirect_back fallback_location: root_path + end + + @product = Product.new(product_params) + + update_categories + + if @product.save + return redirect_to product_path(@product.id) + else + @categories = @product.categories + flash.now[:status] = :error + flash.now[:result_text] = "Product failed to be added" + flash.now[:messages] = @product.errors.messages + render :new, status: 400 + end + end + + def retire + unless @merchant == @product.merchant + flash[:status] = :error + flash[:result_text] = "Unauthorized user" + return redirect_to root_path + end + @product.status == "active" ? @product.status = "retired" : @product.status = "active" + if @product.save + flash[:status] = :success + flash[:result_text] = "Product status was updated to #{@product.status}." + else + flash[:status] = :error + flash[:result_text] = "Product status couldn't be updated." + flash[:messages] = @product.errors.messages + end + return redirect_back fallback_location: root_path + end + + private + + def find_product + @product = Product.find_by_id(params[:id]) + render_404 unless @product + end + + def product_params + return params.permit(:name, :price, :merchant_id, :quantity, :description, :photo_url, :status) + end + + def find_merchant + @merchant = Merchant.find_by(id: session[:merchant_id]) + end + + def get_categories + @categories = Product.categories + end + + def update_categories + if params[:categories] + params[:categories].each do |category| + @product.add_category(category) + end + end + + if params[:category] + @product.add_category(params[:category]) + end + end +end diff --git a/app/controllers/reviews_controller.rb b/app/controllers/reviews_controller.rb new file mode 100644 index 0000000000..7f68676e8a --- /dev/null +++ b/app/controllers/reviews_controller.rb @@ -0,0 +1,50 @@ +class ReviewsController < ApplicationController + def show + end + + def new + @review = Review.new(product_id: params[:product_id]) + + if session[:merchant_id] == @review.product.merchant_id + flash[:status] = :error + flash[:result_text] = "Merchant can't leave reviews of their own products, sorry! Wink-wink." + redirect_to product_path(@review.product_id) #, status: 403 + end + @title = "Review for #{@review.product.name}" + end + + def create + @review = Review.new(review_params) + + if session[:merchant_id] != @review.product.merchant.id + if @review.save + redirect_to product_path(@review.product_id) + else + flash[:status] = :error + flash[:result_text] = "Review failed to be added" + flash[:messages] = @review.errors.messages + redirect_to new_review_path(@review.product_id) + end + else + flash[:status] = :error + flash[:result_text] = "Merchant can't leave reviews of their own products, sorry! Wink-wink." + redirect_to product_path(@review.product_id), status: 403 + end + end + + def edit + end + + def update + end + + def index + end + + private + + def review_params + return params.require(:review).permit(:product_id, :rating, :review_text, :title) + end + +end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb new file mode 100644 index 0000000000..395df7538a --- /dev/null +++ b/app/controllers/sessions_controller.rb @@ -0,0 +1,34 @@ +class SessionsController < ApplicationController + def create + @auth_hash = request.env['omniauth.auth'] + + @merchant = Merchant.find_by(oauth_uid: @auth_hash['uid'], oauth_provider: @auth_hash['provider']) + + if @merchant + session[:merchant_id] = @merchant.id + flash[:status] = :success + flash[:result_text] = "You successfully logged in as #{@merchant.username}." + else + @merchant = Merchant.new oauth_uid: @auth_hash['uid'], oauth_provider: @auth_hash['provider'], username: @auth_hash['info']['nickname'], email: @auth_hash['info']['email'] + if @merchant.save + session[:merchant_id] = @merchant.id + flash[:status] = :success + flash[:result_text] = "You successfully logged in as new user #{@merchant.username}." + else + flash[:status] = :error + flash[:result_text] = "Unable to save user :( " + end + end#ifelse + redirect_to merchant_path(@merchant.id) + end #create + + def logout + session[:merchant_id] = nil + flash[:status] = :success + flash[:result_text] = "You successfully logged out." + redirect_to root_path + end + + # def index + # end +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb new file mode 100644 index 0000000000..b5716bb9ea --- /dev/null +++ b/app/helpers/application_helper.rb @@ -0,0 +1,7 @@ +module ApplicationHelper + + def pretty_date(date) + ("" + date.strftime('%a, %d %b %Y') + "").html_safe + end + +end diff --git a/app/helpers/merchants_helper.rb b/app/helpers/merchants_helper.rb new file mode 100644 index 0000000000..5337747b0f --- /dev/null +++ b/app/helpers/merchants_helper.rb @@ -0,0 +1,2 @@ +module MerchantsHelper +end diff --git a/app/helpers/orders_helper.rb b/app/helpers/orders_helper.rb new file mode 100644 index 0000000000..bd884f9eb4 --- /dev/null +++ b/app/helpers/orders_helper.rb @@ -0,0 +1,58 @@ +module OrdersHelper + def us_states + [ + ['Alabama', 'AL'], + ['Alaska', 'AK'], + ['Arizona', 'AZ'], + ['Arkansas', 'AR'], + ['California', 'CA'], + ['Colorado', 'CO'], + ['Connecticut', 'CT'], + ['Delaware', 'DE'], + ['District of Columbia', 'DC'], + ['Florida', 'FL'], + ['Georgia', 'GA'], + ['Hawaii', 'HI'], + ['Idaho', 'ID'], + ['Illinois', 'IL'], + ['Indiana', 'IN'], + ['Iowa', 'IA'], + ['Kansas', 'KS'], + ['Kentucky', 'KY'], + ['Louisiana', 'LA'], + ['Maine', 'ME'], + ['Maryland', 'MD'], + ['Massachusetts', 'MA'], + ['Michigan', 'MI'], + ['Minnesota', 'MN'], + ['Mississippi', 'MS'], + ['Missouri', 'MO'], + ['Montana', 'MT'], + ['Nebraska', 'NE'], + ['Nevada', 'NV'], + ['New Hampshire', 'NH'], + ['New Jersey', 'NJ'], + ['New Mexico', 'NM'], + ['New York', 'NY'], + ['North Carolina', 'NC'], + ['North Dakota', 'ND'], + ['Ohio', 'OH'], + ['Oklahoma', 'OK'], + ['Oregon', 'OR'], + ['Pennsylvania', 'PA'], + ['Puerto Rico', 'PR'], + ['Rhode Island', 'RI'], + ['South Carolina', 'SC'], + ['South Dakota', 'SD'], + ['Tennessee', 'TN'], + ['Texas', 'TX'], + ['Utah', 'UT'], + ['Vermont', 'VT'], + ['Virginia', 'VA'], + ['Washington', 'WA'], + ['West Virginia', 'WV'], + ['Wisconsin', 'WI'], + ['Wyoming', 'WY'] + ] + end +end diff --git a/app/helpers/products_helper.rb b/app/helpers/products_helper.rb new file mode 100644 index 0000000000..996b751d1c --- /dev/null +++ b/app/helpers/products_helper.rb @@ -0,0 +1,17 @@ +module ProductsHelper + def product_description(product) + if product.description == nil + return ("No description.").html_safe + else + return (product.description).html_safe + end + end + + def product_photo(product) + if product.photo_url != "" && product.photo_url != nil + ("" + product.name + "").html_safe + else + ("" + product.name + "").html_safe + end + end +end diff --git a/app/helpers/reviews_helper.rb b/app/helpers/reviews_helper.rb new file mode 100644 index 0000000000..682b7b1abc --- /dev/null +++ b/app/helpers/reviews_helper.rb @@ -0,0 +1,2 @@ +module ReviewsHelper +end diff --git a/app/helpers/sessions_helper.rb b/app/helpers/sessions_helper.rb new file mode 100644 index 0000000000..309f8b2eb3 --- /dev/null +++ b/app/helpers/sessions_helper.rb @@ -0,0 +1,2 @@ +module SessionsHelper +end diff --git a/app/jobs/application_job.rb b/app/jobs/application_job.rb new file mode 100644 index 0000000000..a009ace51c --- /dev/null +++ b/app/jobs/application_job.rb @@ -0,0 +1,2 @@ +class ApplicationJob < ActiveJob::Base +end diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb new file mode 100644 index 0000000000..286b2239d1 --- /dev/null +++ b/app/mailers/application_mailer.rb @@ -0,0 +1,4 @@ +class ApplicationMailer < ActionMailer::Base + default from: 'from@example.com' + layout 'mailer' +end diff --git a/app/models/application_record.rb b/app/models/application_record.rb new file mode 100644 index 0000000000..10a4cba84d --- /dev/null +++ b/app/models/application_record.rb @@ -0,0 +1,3 @@ +class ApplicationRecord < ActiveRecord::Base + self.abstract_class = true +end diff --git a/app/models/concerns/.keep b/app/models/concerns/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/models/merchant.rb b/app/models/merchant.rb new file mode 100644 index 0000000000..ce4dd1cde6 --- /dev/null +++ b/app/models/merchant.rb @@ -0,0 +1,23 @@ +class Merchant < ApplicationRecord + has_many :products + + validates :username, presence: true + validates :username, uniqueness: true + validates :email, presence: true + validates :email, uniqueness: true + + def join_orderitems(status) + if [nil, ""].include?(status) + return Orderitem.joins('join products on orderitems.product_id = products.id').where("products.merchant_id = #{self.id}").joins('LEFT JOIN orders ON orderitems.order_id = orders.id').where("orders.status IS DISTINCT FROM 'pending'") + else + return Orderitem.joins('join products on orderitems.product_id = products.id').where("products.merchant_id = #{self.id}").joins('LEFT JOIN orders ON orderitems.order_id = orders.id').where("orders.status = '#{status}' AND orders.status IS DISTINCT FROM 'pending'") + end + end + + def self.active_merchants + products = Product.where(status: "active").pluck(:merchant_id).uniq + merchants = products.map { |m| Merchant.find_by(id: m) } + return merchants.sort_by{|m| m.username.downcase} + end + +end diff --git a/app/models/order.rb b/app/models/order.rb new file mode 100644 index 0000000000..2fa85cb614 --- /dev/null +++ b/app/models/order.rb @@ -0,0 +1,104 @@ +class Order < ApplicationRecord + + has_many :orderitems + has_many :products, :through => :orderitems + validates :status, presence: true + + validates :customer_name, presence: { :if => lambda { self.status != "pending"}, message: "name cannot be blank"} + + validates :customer_email, + presence: { + :if => lambda { self.status != "pending"}, + message: "customer email cannot be blank" + }, + format: { + :if => lambda { self.status != "pending"}, + with: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i, + message: "invalid email format" + } + + validates :address1, presence: { :if => lambda { self.status != "pending"}, message: "address cannot be blank"} + + validates :city, + presence: { + :if => lambda { self.status != "pending"}, message: "city cannot be blank" + }, + format: { + :if => lambda { self.status != "pending"}, + with: + /\A(?:[a-zA-Z]+(?:[.'\-,])?\s?)+\z/i, + message: "invalid city name" + } + + validates :state, + presence: { + :if => lambda { self.status != "pending"}, message: "state cannot be blank" + }, + format: { + :if => lambda { self.status != "pending"}, + with: + /\A(?:A[KLRZ]|C[AOT]|D[CE]|FL|GA|HI|I[ADLN]|K[SY]|LA|M[ADEINOST]|N[CDEHJMVY]|O[HKR]|PA|RI|S[CD]|T[NX]|UT|V[AT]|W[AIVY])*\z/, + message: "invalid state abbreviation" + } + + validates :zipcode, + presence: { + :if => lambda { self.status != "pending"}, message: "zipcode cannot be blank" + }, + format: { + :if => lambda { self.status != "pending"}, + with: + /\A([0-9]{5}(?:-[0-9]{4})?)*\z/, + message: "invalid zipcode" + } + + validates :cc_name, presence: { :if => lambda { self.status != "pending"}, message: "name for credit card cannot be blank"} + + + # https://gist.github.com/nerdsrescueme/1237767 + validates :cc_number, + presence: { + :if => lambda { self.status != "pending"}, message: "credit card number cannot be blank" + }, + format: { + :if => lambda { self.status != "pending"}, + with: + /\A(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6011[0-9]{12}|3(?:0[0-5]|[68][0-9])[0-9]{11}|3[47][0-9]{13})\z/, + message: "invalid credit card number" + } + + validates :cc_expiration, + expirationDate: true + + validates :cc_security, + presence: { + :if => lambda { self.status != "pending"}, message: "CVV cannot be blank" + }, + format: { + :if => lambda { self.status != "pending"}, + with: + /\A[0-9]{3,4}\z/, + message: "invalid CVV" + } + + + validates :billingzip, + presence: { + :if => lambda { self.status != "pending"}, message: "billing zipcode cannot be blank" + }, + format: { + :if => lambda { self.status != "pending"}, + with: + /\A([0-9]{5}(?:-[0-9]{4})?)*\z/, + message: "invalid billing zipcode" + } + + def status_check + return if self.status != "paid" + unshipped = self.orderitems.select { |item| item.status != "shipped"} + return if unshipped.length != 0 + self.status = "complete" + self.save + end + +end diff --git a/app/models/orderitem.rb b/app/models/orderitem.rb new file mode 100644 index 0000000000..66bf6dc256 --- /dev/null +++ b/app/models/orderitem.rb @@ -0,0 +1,12 @@ +class Orderitem < ApplicationRecord + belongs_to :product + belongs_to :order + + validates :quantity, presence: true + validates :quantity, numericality: { greater_than: 0 } + validates :quantity, numericality: { only_integer: true } + + def subtotal + (self.quantity * self.product.price).round(2) + end +end diff --git a/app/models/product.rb b/app/models/product.rb new file mode 100644 index 0000000000..ff20679362 --- /dev/null +++ b/app/models/product.rb @@ -0,0 +1,75 @@ +class Product < ApplicationRecord + belongs_to :merchant + has_many :orderitems + has_many :orders, :through => :orderitems + has_many :reviews + + + validates :name, presence: true + validates :name, uniqueness: true + validates :price, presence: true + validates :price, numericality: { greater_than: 0 } + validates :price, numericality: { only_float: true } + #may need to include integers as well + validates :merchant_id, presence: true + validates :quantity, presence: true, numericality: {only_integer: true, greater_than_or_equal_to: 0 } + validates :status, presence: true + validates :status, inclusion: { in: [ "active", "retired" ] } + + + def self.get_products(a_category: "all", a_merchant: "all") + if ["all", nil, ""].include?(a_category) && ["all", nil, ""].include?(a_merchant) + return Product.all + elsif ["all", nil, ""].include?(a_category) + return Product.where(merchant_id: a_merchant) + elsif ["all", nil, ""].include?(a_merchant) + return Product.where("categories @> ?", "{#{a_category}}") + else + # category_products = Product.where("categories @> ?", "{#{a_category}}") + # merchant_products = Product.where(merchant_id: a_merchant) + + + # products = Product.where("categories @> ARRAY#{a_category} AND merchant_id = #{a_merchant}") + # + # products = category_products & merchant_products + return Product.where("products.categories @> ARRAY[?]::varchar[]", a_category) & Product.where(merchant_id: a_merchant) + end + end + + + def self.active_only + return Product.where(status: "active") + end + + # IF NEEDED UNCOMMENT @retired_products IN CONTROLLER + # def self.retired_only + # return Product.where(status: "retired") + # end + + def self.categories + categories = Product.where(status: "active").pluck(:categories).flatten.uniq + return categories.sort_by{|c| c.downcase} + end + + def average_rating + return "No reviews yet" if reviews.count == 0 + sum = 0 + reviews.each do |review| + sum += review.rating + end + return (sum.to_f/reviews.count).round(1) + end + + def add_category(category) + if categories.include?(category) == false && category != "" + categories << category + end + end + + + + # def first_10_reviews + # return reviews.all.limit(10) + # end + +end diff --git a/app/models/review.rb b/app/models/review.rb new file mode 100644 index 0000000000..bb85bc1354 --- /dev/null +++ b/app/models/review.rb @@ -0,0 +1,12 @@ +class Review < ApplicationRecord + belongs_to :product + + validates :rating, presence: true + validates :rating, numericality: { only_integer: true } + validates :rating, numericality: { greater_than_or_equal_to: 1 } + validates :rating, numericality: { less_than_or_equal_to: 5 } + validates :title, presence: true, length: { in: 2..100 } + validates :review_text, presence: true, length: { in: 2..700 } + + +end diff --git a/app/validators/expiration_date_validator.rb b/app/validators/expiration_date_validator.rb new file mode 100644 index 0000000000..2284f3bbe5 --- /dev/null +++ b/app/validators/expiration_date_validator.rb @@ -0,0 +1,18 @@ +class ExpirationDateValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + + return if record.status == "pending" + + if record.cc_expiration == nil + return record.errors[:cc_expiration] << 'expiration date can\'t be blank' + end + + unless value =~ /\A(0[1-9]|1[0-2]|[1-9])\/(20[0-9][0-9]|[0-9][0-9])\z/ + return record.errors[:cc_expiration] << 'invalid expiration date format' + end + + if value[3..4].to_i < DateTime.now.year.to_s[2..3].to_i || (value[3..4].to_i == DateTime.now.year.to_s[2..3].to_i && value[0..1].to_i <= DateTime.now.month.to_i) + record.errors[:cc_expiration] << 'expiration date can\'t be in the past' + end + end +end diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb new file mode 100644 index 0000000000..1405c0cefe --- /dev/null +++ b/app/views/layouts/application.html.erb @@ -0,0 +1,74 @@ + + + + + + + <% if @title != nil %> + Treebae | <%= @title %> + <% else %> + Treebae | For the Modern Treehugger + <% end %> + <%= stylesheet_link_tag "application" %> + <%= javascript_include_tag "application", 'data-turbolinks-track' => true %> + <%= csrf_meta_tags %> + <%= favicon_link_tag %> + + + + +
+ <% if flash[:status] == :error || flash[:status] == "error" %> +

An error occured: <%= flash[:result_text] %>

+ <% end %> + <% if flash[:status] == :success || flash[:status] == "success" %> +

Well done! <%= flash[:result_text] %>

+ <% end %> + + <% if flash[:messages] %> + + <% end %> +
+ +
+ <%= yield %> +
+ + + diff --git a/app/views/layouts/mailer.html.erb b/app/views/layouts/mailer.html.erb new file mode 100644 index 0000000000..cbd34d2e9d --- /dev/null +++ b/app/views/layouts/mailer.html.erb @@ -0,0 +1,13 @@ + + + + + + + + + <%= yield %> + + diff --git a/app/views/layouts/mailer.text.erb b/app/views/layouts/mailer.text.erb new file mode 100644 index 0000000000..37f0bddbd7 --- /dev/null +++ b/app/views/layouts/mailer.text.erb @@ -0,0 +1 @@ +<%= yield %> diff --git a/app/views/merchants/create.html.erb b/app/views/merchants/create.html.erb new file mode 100644 index 0000000000..1762feece0 --- /dev/null +++ b/app/views/merchants/create.html.erb @@ -0,0 +1,2 @@ +

Merchants#create

+

Find me in app/views/merchants/create.html.erb

diff --git a/app/views/merchants/edit.html.erb b/app/views/merchants/edit.html.erb new file mode 100644 index 0000000000..825f2251dc --- /dev/null +++ b/app/views/merchants/edit.html.erb @@ -0,0 +1,2 @@ +

Merchants#edit

+

Find me in app/views/merchants/edit.html.erb

diff --git a/app/views/merchants/fulfillment.html.erb b/app/views/merchants/fulfillment.html.erb new file mode 100644 index 0000000000..738ed27e09 --- /dev/null +++ b/app/views/merchants/fulfillment.html.erb @@ -0,0 +1,62 @@ +

Manage Your Orders

+

Total Revenue: <%= number_to_currency(@total_revenue) %>

+ + + + + + + + + <% @revenue_by_status.each do |status, revenue| %> + + + + + + <% end %> +
StatusRevenue
<%= status %><%= number_to_currency(@total_revenue) %>
+ +<%= form_tag(merchant_fulfillment_path, method: :get) do %> + <%= label_tag(:status, "Status") %> + <%= select_tag "status", options_for_select(@statuses, params["status"]), include_blank: "all" %> + <%= submit_tag("Submit", class: "button") %> +<% end %> +<% if @orders.length > 0 %> + + + + + + + + + + + + <% @orders.each do |order_id, status_revenue_hash| %> + + + + + + + <% if status_revenue_hash[:status] == "cancelled" %> + + <% elsif status_revenue_hash[:unshipped] == true %> + + <% else %> + + <% end %> +
Order#Purchase DateStatusRevenueItems SoldOrder#
<%= link_to order_id, merchant_order_path(@merchant.id, order_id)%><%= status_revenue_hash[:purchase_date] %><%= status_revenue_hash[:status] %><%= number_to_currency(status_revenue_hash[:revenue]) %> +
    + <% status_revenue_hash[:items].each do |item| %> +
  • + <%= item.quantity %> <%= link_to item.product.name, product_path(item.product_id) %>: <%= number_to_currency(item.product.price * item.quantity) %> +
  • + <% end %> +
+
Order was cancelled<%= link_to "Mark shipped", merchant_ship_path(@merchant.id, order_id), method: :patch, class: 'button' %>Products have been shipped + <% end %> +
+<% end %> diff --git a/app/views/merchants/index.html.erb b/app/views/merchants/index.html.erb new file mode 100644 index 0000000000..0ad7977813 --- /dev/null +++ b/app/views/merchants/index.html.erb @@ -0,0 +1,2 @@ +

Merchants#index

+

Find me in app/views/merchants/index.html.erb

diff --git a/app/views/merchants/new.html.erb b/app/views/merchants/new.html.erb new file mode 100644 index 0000000000..aaac3f85d7 --- /dev/null +++ b/app/views/merchants/new.html.erb @@ -0,0 +1,2 @@ +

Merchants#new

+

Find me in app/views/merchants/new.html.erb

diff --git a/app/views/merchants/show.html.erb b/app/views/merchants/show.html.erb new file mode 100644 index 0000000000..6095a6f90e --- /dev/null +++ b/app/views/merchants/show.html.erb @@ -0,0 +1,34 @@ +

<%=@merchant.username%>'s treeBae

+
+ + +<% if @merchant.products.count > 0 %> + + + + + + + + + + + <% @merchant.products.each do |product| %> + + + + + + + + <% end %> +
ProductQuantitiesLaunchedUpdateRetire
<%= link_to product.name.capitalize, product_path(product.id) %><%= product.quantity %><%= pretty_date(product.created_at) %><%= link_to "Update", edit_product_path(product.id), class: "button" %><%= link_to (product.status == "active") ? "Retire" : "Activate", retire_path(product.id), class: "button" %>
+ <% else %> +

Don't be a stick in the mud, <%= link_to "create", new_product_path%> a product!

+ <% end %> +
+<%= link_to "Create a product", new_product_path, class: "button" %> +<%= link_to "Fulfillment", merchant_fulfillment_path(@merchant.id) ,class: "button" %> <%# TODO: ADD ORDER FUFILLEMENT PATH %> diff --git a/app/views/merchants/show_order.html.erb b/app/views/merchants/show_order.html.erb new file mode 100644 index 0000000000..aa6a5c9082 --- /dev/null +++ b/app/views/merchants/show_order.html.erb @@ -0,0 +1,41 @@ +

Order: <%= @order.id %>

+

Order Details

+ + +

Mailing Address

+

+ <% address_connector = ((@order.address2 == "" || @order.address2 == nil) ? ", " : ", #{@order.address2}, ")%> + <%= @order.address1 + address_connector + @order.city + ", " + @order.state + " " + @order.zipcode %> +

+ +

Purchased Items

+ + + + + + + + + <% @orderitems.each do |item| %> + + + + + + <% end %> +
ProductQuantitySubtotal
<%= link_to item.product.name, product_path(item.product_id) %><%= item.quantity %><%= number_to_currency(item.product.price * item.quantity) %>
diff --git a/app/views/merchants/update.html.erb b/app/views/merchants/update.html.erb new file mode 100644 index 0000000000..48e3f8aed5 --- /dev/null +++ b/app/views/merchants/update.html.erb @@ -0,0 +1,2 @@ +

Merchants#update

+

Find me in app/views/merchants/update.html.erb

diff --git a/app/views/orders/checkout_form.html.erb b/app/views/orders/checkout_form.html.erb new file mode 100644 index 0000000000..2d64052eca --- /dev/null +++ b/app/views/orders/checkout_form.html.erb @@ -0,0 +1,43 @@ +

Checkout

+
+

Personal Information

+ <%= form_tag(checkout_path, method: :patch) do %> + <%= label_tag(:customer_name, "Name") %> + <%= text_field_tag(:customer_name) %> + + <%= label_tag(:customer_email, "Email") %> + <%= email_field_tag(:customer_email) %> + + <%= label_tag(:address1, "Address 1") %> + <%= text_field_tag :address1 %> + + <%= label_tag(:address2, "Address 2") %> + <%= text_field_tag :address2 %> + + <%= label_tag(:city, "City") %> + <%= text_field_tag :city %> + + <%= label_tag(:state, "State") %> + <%= select_tag :state, options_for_select(us_states) %> + + <%= label_tag(:zipcode, "Zip code") %> + <%= text_field_tag :zipcode, nil, maxlength: 5 %> +

Payment Information

+ <%= label_tag(:cc_name, "Name on credit card") %> + <%= text_field_tag :cc_name %> + + <%= label_tag(:cc_number, "Credit card number") %> + <%= text_field_tag :cc_number, nil, minlength: 16, maxlength: 16 %> + + <%= label_tag(:cc_expiration, "Expiration date on credit card") %> + <%= text_field_tag :cc_expiration %> + + <%= label_tag(:cc_security, "CVV code") %> + <%= text_field_tag :cc_security, nil, minlength: 3, maxlenth: 3 %> + + <%= label_tag(:billingzip, "Billing zip code") %> + <%= text_field_tag :billingzip, nil, maxlength: 5 %> + + <%= submit_tag("Submit", class: "button", method: :patch) %> + <% end %> +
diff --git a/app/views/orders/confirmation.html.erb b/app/views/orders/confirmation.html.erb new file mode 100644 index 0000000000..18f475ebb0 --- /dev/null +++ b/app/views/orders/confirmation.html.erb @@ -0,0 +1,13 @@ +

Thank you for your order!

+

Order Status: <%= @order.status.titleize %>

+

Order Placed: <%= @order.purchase_datetime %>

+ +Total: <%= number_to_currency(@total) %> diff --git a/app/views/orders/create.html.erb b/app/views/orders/create.html.erb new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/app/views/orders/create.html.erb @@ -0,0 +1 @@ + diff --git a/app/views/orders/edit.html.erb b/app/views/orders/edit.html.erb new file mode 100644 index 0000000000..7de9049bee --- /dev/null +++ b/app/views/orders/edit.html.erb @@ -0,0 +1,2 @@ +

Orders#edit

+

Find me in app/views/orders/edit.html.erb

diff --git a/app/views/orders/index.html.erb b/app/views/orders/index.html.erb new file mode 100644 index 0000000000..d63a69fb54 --- /dev/null +++ b/app/views/orders/index.html.erb @@ -0,0 +1,2 @@ +

Orders#index

+

Find me in app/views/orders/index.html.erb

diff --git a/app/views/orders/new.html.erb b/app/views/orders/new.html.erb new file mode 100644 index 0000000000..1bc27609ce --- /dev/null +++ b/app/views/orders/new.html.erb @@ -0,0 +1,2 @@ +

Orders#new

+

Find me in app/views/orders/new.html.erb

diff --git a/app/views/orders/show.html.erb b/app/views/orders/show.html.erb new file mode 100644 index 0000000000..22eb495f6f --- /dev/null +++ b/app/views/orders/show.html.erb @@ -0,0 +1,2 @@ +

Orders#show

+

Find me in app/views/orders/show.html.erb

diff --git a/app/views/orders/show_cart.html.erb b/app/views/orders/show_cart.html.erb new file mode 100644 index 0000000000..8fa1ed17ba --- /dev/null +++ b/app/views/orders/show_cart.html.erb @@ -0,0 +1,47 @@ +
+

<% if @cart.orderitems.length < 1 %> +

Shopping cart is empty

+ + <% elsif @cart.orderitems.length > 0 %> + Shopping Cart +

+
+
+
+

Product

+

Quantity

+

Subtotal

+

Remove From Cart +

+ +
+ <% if @cart.orderitems %> + <% @cart.orderitems.each do |order_item| %> + + <% end %> + <% end %> +
+
+ +
+ +
+ <% end %> diff --git a/app/views/orders/update.html.erb b/app/views/orders/update.html.erb new file mode 100644 index 0000000000..21caac1f70 --- /dev/null +++ b/app/views/orders/update.html.erb @@ -0,0 +1,2 @@ +

Orders#update

+

Find me in app/views/orders/update.html.erb

diff --git a/app/views/products/_form.html.erb b/app/views/products/_form.html.erb new file mode 100644 index 0000000000..3b440382db --- /dev/null +++ b/app/views/products/_form.html.erb @@ -0,0 +1,36 @@ +
+<%= form_tag(path, method: method) do %> + + <%= hidden_field_tag(:merchant_id, @merchant.id) %> + + <%= label_tag(:name, "Name") %> + <%= text_field_tag(:name, @product.name, :maxlength => 30) %> + + <%= label_tag(:price, "Price") %> + <%= number_field_tag(:price, @product.price, min: 0.01, step: 0.01) %> + + <%= label_tag(:quantity, "Quantity") %> + <%= number_field_tag(:quantity, @product.quantity, min: 0, step: 1) %> + + <%= label_tag(:categories, "Categories") %> + <%= select_tag "categories", options_for_select(@categories, params["categories"]), include_blank: "none", :multiple => true %> + <%= label_tag(:category, "Category") %> + <%= text_field_tag(:category) %> + + <%= radio_button_tag(:status, "active", @product.status == "active") %> + <%= label_tag(:status, "Active")%> + <%= radio_button_tag(:status, "retired", @product.status == "retired") %> + <%= label_tag(:status, "Retired") %> + + <%= label_tag(:description) %> + <%= text_field_tag(:description, @product.description) %> + + <%= label_tag(:photo_url, "Image URL: ") %> + <%= text_field_tag(:photo_url, @product.photo_url) %> + + <%= submit_tag(submit_name, class: "button", :maxlength => 30) %> + + <%= link_to "Cancel", root_path, method: :get, class: 'button' %> + +<% end %> +
diff --git a/app/views/products/edit.html.erb b/app/views/products/edit.html.erb new file mode 100644 index 0000000000..47609ef9af --- /dev/null +++ b/app/views/products/edit.html.erb @@ -0,0 +1,3 @@ +

Edit your product: <%= @product.name %>

+ +<%= render partial: "form", locals: { path: product_path(@product.id), submit_name: "Edit product", method: :patch }%> diff --git a/app/views/products/index.html.erb b/app/views/products/index.html.erb new file mode 100644 index 0000000000..f310962011 --- /dev/null +++ b/app/views/products/index.html.erb @@ -0,0 +1,53 @@ +

treebae Deals

+<%= form_tag(root_path, method: :get) do %> +
+ <%= form_tag(root_path, method: :get) do %> +
+ <%= label_tag(:category, "Category") %> + <%= select_tag "category", options_for_select(@categories, params["category"]), include_blank: "all" %> +
+
+ <%= label_tag(:merchant, "Merchant") %> + <%= select_tag "merchant", options_from_collection_for_select(Merchant.active_merchants, "id", "username", selected: params[:merchant].to_i), include_blank: "all" %> +
+
+ <%= submit_tag("Search", class: "button") %> +
+ <% end %> +
+
+ <%= will_paginate @products %> +
+
+ <% @products.each do |product| %> +
+
+ <%= product_photo(product) %> +
+
+

<%= link_to(product.name.capitalize, product_path(product.id)) %>

+ <% if product.reviews.count > 0 %> +
+ " > + + +
+ +
+
+ + <%= product.reviews.count %> + + <% else %> + No reviews + <% end %> +
<%= number_to_currency(product.price) %>
+ <%= link_to "Add to cart", add_order_item_path(session[:order_id], product.id), method: :patch, class: :button %> +
+
+ <% end %> + <% end %> +
+
+ <%= will_paginate @products %> +
diff --git a/app/views/products/new.html.erb b/app/views/products/new.html.erb new file mode 100644 index 0000000000..5662136cb9 --- /dev/null +++ b/app/views/products/new.html.erb @@ -0,0 +1,3 @@ +

Create a new product

+ +<%= render partial: "form", locals: {path: products_path(@product.id), submit_name: "Add product", method: :post }%> diff --git a/app/views/products/show.html.erb b/app/views/products/show.html.erb new file mode 100644 index 0000000000..50a1a2d760 --- /dev/null +++ b/app/views/products/show.html.erb @@ -0,0 +1,109 @@ +
+
+ <%= product_photo(@product) %> +
+ +
+
+

<%= @product.name %>

+ <% if @product.status == "retired" %> +

Retired product

+ <% end %> + <% if @product.reviews.count > 0 %> +
+ " > + + +
+ +
+
+ <% end %> +
+ <% if @merchant == @product.merchant %> + <%= link_to "Edit Product", edit_product_path(@product.id), class: :button %> + <% else %> + <%= link_to "Add to cart", add_order_item_path(session[:order_id], @product.id), method: :patch, class: :button %> + <% end %> +
+
+ <% if @product.status == "retired" %> +

Retired product

+ <% end %> + +

List Price: <%= number_to_currency(@product.price) %>

+ <% if @product.quantity <= 5 %> +

Limited - Only <%= @product.quantity %> left in stock

+ <% end %> +

Ships from and sold by: <%= link_to(@product.merchant.username, products_path(:merchant => @product.merchant.id)) %>

+ +

Description:

+

<%= product_description(@product) %>

+ <% if @product.categories != nil %> + Categories: + <% count = 0 %> + <% @product.categories.each do |category| %> + <% count += 1 %> + <% if count < @product.categories.length %> + <%= link_to(category, products_path(:category => category)) %>, + <% else %> + <%= link_to(category, products_path(:category => category)) %> + <% end %> + <% end %> + <% end %> +
+
+ +
+
+
+

Average Rating:

+ <% if @product.reviews.count > 0 %> +
+ " > + + +
+ +
+
+ + <%= @product.average_rating %> + + <% else %> +

No rating yet.

+ <% end %> + <% if @merchant != @product.merchant %> + + <%= link_to("Leave a review", new_review_path(@product.id), method: :get, class: "button") %> + <% end %> + +
+

Reviews:

+ <% if @product.reviews.count < 1 %> +

Be the first to review this item

+ <% else %> +
    + <% @product.reviews.each do |review| %> +
    +
  • + <% review.rating.times do %> + + <% end %> + <% (5 - review.rating).times do %> + + <% end %> + + <%=review.title%> +
  • On: <%=pretty_date(review.created_at)%>
  • + +
  • <%=review.review_text%>
  • +
    + + <% end %> +
+ <% end %> +
+
+ +
diff --git a/app/views/reviews/create.html.erb b/app/views/reviews/create.html.erb new file mode 100644 index 0000000000..44bf11c2ed --- /dev/null +++ b/app/views/reviews/create.html.erb @@ -0,0 +1,2 @@ +

Reviews#create

+

Find me in app/views/reviews/create.html.erb

diff --git a/app/views/reviews/edit.html.erb b/app/views/reviews/edit.html.erb new file mode 100644 index 0000000000..28034f5e02 --- /dev/null +++ b/app/views/reviews/edit.html.erb @@ -0,0 +1,2 @@ +

Reviews#edit

+

Find me in app/views/reviews/edit.html.erb

diff --git a/app/views/reviews/index.html.erb b/app/views/reviews/index.html.erb new file mode 100644 index 0000000000..d19455779d --- /dev/null +++ b/app/views/reviews/index.html.erb @@ -0,0 +1,2 @@ +

Reviews#index

+

Find me in app/views/reviews/index.html.erb

diff --git a/app/views/reviews/new.html.erb b/app/views/reviews/new.html.erb new file mode 100644 index 0000000000..ffb26a7654 --- /dev/null +++ b/app/views/reviews/new.html.erb @@ -0,0 +1,23 @@ +

Leave a review for <%= @review.product.name %>

+ +<%= form_for @review do |f| %> + <%= f.hidden_field :product_id %> + <%= f.label :rating %> +
+ <%= f.radio_button(:rating, 5) %> + + <%= f.radio_button(:rating, 4) %> + + <%= f.radio_button(:rating, 3) %> + + <%= f.radio_button(:rating, 2) %> + + <%= f.radio_button(:rating, 1) %> + +
+ <%= f.label :Title %> + <%= f.text_field :title %> + <%= f.label :Review %> + <%= f.text_area :review_text %> + <%= f.submit("submit", class: "button") %> +<% end %> diff --git a/app/views/reviews/show.html.erb b/app/views/reviews/show.html.erb new file mode 100644 index 0000000000..68d8d94c3b --- /dev/null +++ b/app/views/reviews/show.html.erb @@ -0,0 +1,2 @@ +

Reviews#show

+

Find me in app/views/reviews/show.html.erb

diff --git a/app/views/reviews/update.html.erb b/app/views/reviews/update.html.erb new file mode 100644 index 0000000000..7d46cbed5c --- /dev/null +++ b/app/views/reviews/update.html.erb @@ -0,0 +1,2 @@ +

Reviews#update

+

Find me in app/views/reviews/update.html.erb

diff --git a/app/views/sessions/create.html.erb b/app/views/sessions/create.html.erb new file mode 100644 index 0000000000..c251174fea --- /dev/null +++ b/app/views/sessions/create.html.erb @@ -0,0 +1,2 @@ +

Sessions#create

+

Find me in app/views/sessions/create.html.erb

diff --git a/app/views/sessions/index.html.erb b/app/views/sessions/index.html.erb new file mode 100644 index 0000000000..62cbf379e3 --- /dev/null +++ b/app/views/sessions/index.html.erb @@ -0,0 +1,2 @@ +

Sessions#index

+

Find me in app/views/sessions/index.html.erb

diff --git a/bin/bundle b/bin/bundle new file mode 100755 index 0000000000..66e9889e8b --- /dev/null +++ b/bin/bundle @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) +load Gem.bin_path('bundler', 'bundle') diff --git a/bin/rails b/bin/rails new file mode 100755 index 0000000000..5badb2fde0 --- /dev/null +++ b/bin/rails @@ -0,0 +1,9 @@ +#!/usr/bin/env ruby +begin + load File.expand_path('../spring', __FILE__) +rescue LoadError => e + raise unless e.message.include?('spring') +end +APP_PATH = File.expand_path('../config/application', __dir__) +require_relative '../config/boot' +require 'rails/commands' diff --git a/bin/rake b/bin/rake new file mode 100755 index 0000000000..d87d5f5781 --- /dev/null +++ b/bin/rake @@ -0,0 +1,9 @@ +#!/usr/bin/env ruby +begin + load File.expand_path('../spring', __FILE__) +rescue LoadError => e + raise unless e.message.include?('spring') +end +require_relative '../config/boot' +require 'rake' +Rake.application.run diff --git a/bin/setup b/bin/setup new file mode 100755 index 0000000000..78c4e861dc --- /dev/null +++ b/bin/setup @@ -0,0 +1,38 @@ +#!/usr/bin/env ruby +require 'pathname' +require 'fileutils' +include FileUtils + +# path to your application root. +APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) + +def system!(*args) + system(*args) || abort("\n== Command #{args} failed ==") +end + +chdir APP_ROOT do + # This script is a starting point to setup your application. + # Add necessary setup steps to this file. + + puts '== Installing dependencies ==' + system! 'gem install bundler --conservative' + system('bundle check') || system!('bundle install') + + # Install JavaScript dependencies if using Yarn + # system('bin/yarn') + + + # puts "\n== Copying sample files ==" + # unless File.exist?('config/database.yml') + # cp 'config/database.yml.sample', 'config/database.yml' + # end + + puts "\n== Preparing database ==" + system! 'bin/rails db:setup' + + puts "\n== Removing old logs and tempfiles ==" + system! 'bin/rails log:clear tmp:clear' + + puts "\n== Restarting application server ==" + system! 'bin/rails restart' +end diff --git a/bin/spring b/bin/spring new file mode 100755 index 0000000000..fb2ec2ebb4 --- /dev/null +++ b/bin/spring @@ -0,0 +1,17 @@ +#!/usr/bin/env ruby + +# This file loads spring without using Bundler, in order to be fast. +# It gets overwritten when you run the `spring binstub` command. + +unless defined?(Spring) + require 'rubygems' + require 'bundler' + + lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read) + spring = lockfile.specs.detect { |spec| spec.name == "spring" } + if spring + Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path + gem 'spring', spring.version + require 'spring/binstub' + end +end diff --git a/bin/update b/bin/update new file mode 100755 index 0000000000..a8e4462f20 --- /dev/null +++ b/bin/update @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby +require 'pathname' +require 'fileutils' +include FileUtils + +# path to your application root. +APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) + +def system!(*args) + system(*args) || abort("\n== Command #{args} failed ==") +end + +chdir APP_ROOT do + # This script is a way to update your development environment automatically. + # Add necessary update steps to this file. + + puts '== Installing dependencies ==' + system! 'gem install bundler --conservative' + system('bundle check') || system!('bundle install') + + puts "\n== Updating database ==" + system! 'bin/rails db:migrate' + + puts "\n== Removing old logs and tempfiles ==" + system! 'bin/rails log:clear tmp:clear' + + puts "\n== Restarting application server ==" + system! 'bin/rails restart' +end diff --git a/bin/yarn b/bin/yarn new file mode 100755 index 0000000000..c2bacef836 --- /dev/null +++ b/bin/yarn @@ -0,0 +1,11 @@ +#!/usr/bin/env ruby +VENDOR_PATH = File.expand_path('..', __dir__) +Dir.chdir(VENDOR_PATH) do + begin + exec "yarnpkg #{ARGV.join(" ")}" + rescue Errno::ENOENT + $stderr.puts "Yarn executable was not detected in the system." + $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" + exit 1 + end +end diff --git a/config.ru b/config.ru new file mode 100644 index 0000000000..f7ba0b527b --- /dev/null +++ b/config.ru @@ -0,0 +1,5 @@ +# This file is used by Rack-based servers to start the application. + +require_relative 'config/environment' + +run Rails.application diff --git a/config/application.rb b/config/application.rb new file mode 100644 index 0000000000..0bf8489001 --- /dev/null +++ b/config/application.rb @@ -0,0 +1,27 @@ +require_relative 'boot' + +require 'rails/all' + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(*Rails.groups) + +module Betsy + class Application < Rails::Application + config.generators do |g| + # Force new test files to be generated in the minitest-spec style + g.test_framework :minitest, spec: true + + # Always use .js files, never .coffee + g.javascript_engine :js + end + # Initialize configuration defaults for originally generated Rails version. + config.load_defaults 5.1 + + # Settings in config/environments/* take precedence over those specified here. + # Application configuration should go into files in config/initializers + # -- all .rb files in that directory are automatically loaded. + + config.autoload_paths += [Rails.root.join('app', 'validators').to_s] + end +end diff --git a/config/boot.rb b/config/boot.rb new file mode 100644 index 0000000000..30f5120df6 --- /dev/null +++ b/config/boot.rb @@ -0,0 +1,3 @@ +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) + +require 'bundler/setup' # Set up gems listed in the Gemfile. diff --git a/config/cable.yml b/config/cable.yml new file mode 100644 index 0000000000..3cba994bb2 --- /dev/null +++ b/config/cable.yml @@ -0,0 +1,10 @@ +development: + adapter: async + +test: + adapter: async + +production: + adapter: redis + url: redis://localhost:6379/1 + channel_prefix: betsy_production diff --git a/config/database.yml b/config/database.yml new file mode 100644 index 0000000000..6903bb6083 --- /dev/null +++ b/config/database.yml @@ -0,0 +1,85 @@ +# PostgreSQL. Versions 9.1 and up are supported. +# +# Install the pg driver: +# gem install pg +# On OS X with Homebrew: +# gem install pg -- --with-pg-config=/usr/local/bin/pg_config +# On OS X with MacPorts: +# gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config +# On Windows: +# gem install pg +# Choose the win32 build. +# Install PostgreSQL and put its /bin directory on your path. +# +# Configure Using Gemfile +# gem 'pg' +# +default: &default + adapter: postgresql + encoding: unicode + # For details on connection pooling, see Rails configuration guide + # http://guides.rubyonrails.org/configuring.html#database-pooling + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + +development: + <<: *default + database: betsy_development + + # The specified database role being used to connect to postgres. + # To create additional roles in postgres see `$ createuser --help`. + # When left blank, postgres will use the default role. This is + # the same name as the operating system user that initialized the database. + #username: betsy + + # The password associated with the postgres role (username). + #password: + + # Connect on a TCP socket. Omitted by default since the client uses a + # domain socket that doesn't need configuration. Windows does not have + # domain sockets, so uncomment these lines. + #host: localhost + + # The TCP port the server listens on. Defaults to 5432. + # If your server runs on a different port number, change accordingly. + #port: 5432 + + # Schema search path. The server defaults to $user,public + #schema_search_path: myapp,sharedapp,public + + # Minimum log levels, in increasing order: + # debug5, debug4, debug3, debug2, debug1, + # log, notice, warning, error, fatal, and panic + # Defaults to warning. + #min_messages: notice + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: betsy_test + +# As with config/secrets.yml, you never want to store sensitive information, +# like your database password, in your source code. If your source code is +# ever seen by anyone, they now have access to your database. +# +# Instead, provide the password as a unix environment variable when you boot +# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database +# for a full rundown on how to provide these environment variables in a +# production deployment. +# +# On Heroku and other platform providers, you may have a full connection URL +# available as an environment variable. For example: +# +# DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase" +# +# You can use this database configuration with: +# +# production: +# url: <%= ENV['DATABASE_URL'] %> +# +production: + <<: *default + database: betsy_production + username: betsy + password: <%= ENV['BETSY_DATABASE_PASSWORD'] %> diff --git a/config/environment.rb b/config/environment.rb new file mode 100644 index 0000000000..426333bb46 --- /dev/null +++ b/config/environment.rb @@ -0,0 +1,5 @@ +# Load the Rails application. +require_relative 'application' + +# Initialize the Rails application. +Rails.application.initialize! diff --git a/config/environments/development.rb b/config/environments/development.rb new file mode 100644 index 0000000000..5187e22186 --- /dev/null +++ b/config/environments/development.rb @@ -0,0 +1,54 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # In the development environment your application's code is reloaded on + # every request. This slows down response time but is perfect for development + # since you don't have to restart the web server when you make code changes. + config.cache_classes = false + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports. + config.consider_all_requests_local = true + + # Enable/disable caching. By default caching is disabled. + if Rails.root.join('tmp/caching-dev.txt').exist? + config.action_controller.perform_caching = true + + config.cache_store = :memory_store + config.public_file_server.headers = { + 'Cache-Control' => "public, max-age=#{2.days.seconds.to_i}" + } + else + config.action_controller.perform_caching = false + + config.cache_store = :null_store + end + + # Don't care if the mailer can't send. + config.action_mailer.raise_delivery_errors = false + + config.action_mailer.perform_caching = false + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + # Debug mode disables concatenation and preprocessing of assets. + # This option may cause significant delays in view rendering with a large + # number of complex assets. + config.assets.debug = true + + # Suppress logger output for asset requests. + config.assets.quiet = true + + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true + + # Use an evented file watcher to asynchronously detect changes in source code, + # routes, locales, etc. This feature depends on the listen gem. + config.file_watcher = ActiveSupport::EventedFileUpdateChecker +end diff --git a/config/environments/production.rb b/config/environments/production.rb new file mode 100644 index 0000000000..9284f84839 --- /dev/null +++ b/config/environments/production.rb @@ -0,0 +1,91 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.cache_classes = true + + # Eager load code on boot. This eager loads most of Rails and + # your application in memory, allowing both threaded web servers + # and those relying on copy on write to perform better. + # Rake tasks automatically ignore this option for performance. + config.eager_load = true + + # Full error reports are disabled and caching is turned on. + config.consider_all_requests_local = false + config.action_controller.perform_caching = true + + # Attempt to read encrypted secrets from `config/secrets.yml.enc`. + # Requires an encryption key in `ENV["RAILS_MASTER_KEY"]` or + # `config/secrets.yml.key`. + config.read_encrypted_secrets = true + + # Disable serving static files from the `/public` folder by default since + # Apache or NGINX already handles this. + config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? + + # Compress JavaScripts and CSS. + config.assets.js_compressor = :uglifier + # config.assets.css_compressor = :sass + + # Do not fallback to assets pipeline if a precompiled asset is missed. + config.assets.compile = false + + # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.action_controller.asset_host = 'http://assets.example.com' + + # Specifies the header that your server uses for sending files. + # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache + # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX + + # Mount Action Cable outside main process or domain + # config.action_cable.mount_path = nil + # config.action_cable.url = 'wss://example.com/cable' + # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + # config.force_ssl = true + + # Use the lowest log level to ensure availability of diagnostic information + # when problems arise. + config.log_level = :debug + + # Prepend all log lines with the following tags. + config.log_tags = [ :request_id ] + + # Use a different cache store in production. + # config.cache_store = :mem_cache_store + + # Use a real queuing backend for Active Job (and separate queues per environment) + # config.active_job.queue_adapter = :resque + # config.active_job.queue_name_prefix = "betsy_#{Rails.env}" + config.action_mailer.perform_caching = false + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Send deprecation notices to registered listeners. + config.active_support.deprecation = :notify + + # Use default logging formatter so that PID and timestamp are not suppressed. + config.log_formatter = ::Logger::Formatter.new + + # Use a different logger for distributed setups. + # require 'syslog/logger' + # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') + + if ENV["RAILS_LOG_TO_STDOUT"].present? + logger = ActiveSupport::Logger.new(STDOUT) + logger.formatter = config.log_formatter + config.logger = ActiveSupport::TaggedLogging.new(logger) + end + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false +end diff --git a/config/environments/test.rb b/config/environments/test.rb new file mode 100644 index 0000000000..8e5cbde533 --- /dev/null +++ b/config/environments/test.rb @@ -0,0 +1,42 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # The test environment is used exclusively to run your application's + # test suite. You never need to work with it otherwise. Remember that + # your test database is "scratch space" for the test suite and is wiped + # and recreated between test runs. Don't rely on the data there! + config.cache_classes = true + + # Do not eager load code on boot. This avoids loading your whole application + # just for the purpose of running a single test. If you are using a tool that + # preloads Rails for running tests, you may have to set it to true. + config.eager_load = false + + # Configure public file server for tests with Cache-Control for performance. + config.public_file_server.enabled = true + config.public_file_server.headers = { + 'Cache-Control' => "public, max-age=#{1.hour.seconds.to_i}" + } + + # Show full error reports and disable caching. + config.consider_all_requests_local = true + config.action_controller.perform_caching = false + + # Raise exceptions instead of rendering exception templates. + config.action_dispatch.show_exceptions = false + + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false + config.action_mailer.perform_caching = false + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true +end diff --git a/config/initializers/application_controller_renderer.rb b/config/initializers/application_controller_renderer.rb new file mode 100644 index 0000000000..89d2efab2b --- /dev/null +++ b/config/initializers/application_controller_renderer.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +# ActiveSupport::Reloader.to_prepare do +# ApplicationController.renderer.defaults.merge!( +# http_host: 'example.org', +# https: false +# ) +# end diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb new file mode 100644 index 0000000000..4b828e80cb --- /dev/null +++ b/config/initializers/assets.rb @@ -0,0 +1,14 @@ +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = '1.0' + +# Add additional assets to the asset load path. +# Rails.application.config.assets.paths << Emoji.images_path +# Add Yarn node_modules folder to the asset load path. +Rails.application.config.assets.paths << Rails.root.join('node_modules') + +# Precompile additional assets. +# application.js, application.css, and all non-JS/CSS in the app/assets +# folder are already added. +# Rails.application.config.assets.precompile += %w( admin.js admin.css ) diff --git a/config/initializers/backtrace_silencers.rb b/config/initializers/backtrace_silencers.rb new file mode 100644 index 0000000000..59385cdf37 --- /dev/null +++ b/config/initializers/backtrace_silencers.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. +# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } + +# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. +# Rails.backtrace_cleaner.remove_silencers! diff --git a/config/initializers/cookies_serializer.rb b/config/initializers/cookies_serializer.rb new file mode 100644 index 0000000000..5a6a32d371 --- /dev/null +++ b/config/initializers/cookies_serializer.rb @@ -0,0 +1,5 @@ +# Be sure to restart your server when you modify this file. + +# Specify a serializer for the signed and encrypted cookie jars. +# Valid options are :json, :marshal, and :hybrid. +Rails.application.config.action_dispatch.cookies_serializer = :json diff --git a/config/initializers/filter_parameter_logging.rb b/config/initializers/filter_parameter_logging.rb new file mode 100644 index 0000000000..4a994e1e7b --- /dev/null +++ b/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Configure sensitive parameters which will be filtered from the log file. +Rails.application.config.filter_parameters += [:password] diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb new file mode 100644 index 0000000000..ac033bf9dc --- /dev/null +++ b/config/initializers/inflections.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, '\1en' +# inflect.singular /^(ox)en/i, '\1' +# inflect.irregular 'person', 'people' +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym 'RESTful' +# end diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb new file mode 100644 index 0000000000..dc1899682b --- /dev/null +++ b/config/initializers/mime_types.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Add new mime types for use in respond_to blocks: +# Mime::Type.register "text/richtext", :rtf diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb new file mode 100644 index 0000000000..fd4416122a --- /dev/null +++ b/config/initializers/omniauth.rb @@ -0,0 +1,3 @@ +Rails.application.config.middleware.use OmniAuth::Builder do + provider :github, ENV["GITHUB_CLIENT_ID"], ENV["GITHUB_CLIENT_SECRET"], scope: "user:email" +end diff --git a/config/initializers/will_paginate_array_fix.rb b/config/initializers/will_paginate_array_fix.rb new file mode 100644 index 0000000000..dfed3fe9e1 --- /dev/null +++ b/config/initializers/will_paginate_array_fix.rb @@ -0,0 +1 @@ +require 'will_paginate/array' diff --git a/config/initializers/wrap_parameters.rb b/config/initializers/wrap_parameters.rb new file mode 100644 index 0000000000..bbfc3961bf --- /dev/null +++ b/config/initializers/wrap_parameters.rb @@ -0,0 +1,14 @@ +# Be sure to restart your server when you modify this file. + +# This file contains settings for ActionController::ParamsWrapper which +# is enabled by default. + +# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. +ActiveSupport.on_load(:action_controller) do + wrap_parameters format: [:json] +end + +# To enable root element in JSON for ActiveRecord objects. +# ActiveSupport.on_load(:active_record) do +# self.include_root_in_json = true +# end diff --git a/config/locales/en.yml b/config/locales/en.yml new file mode 100644 index 0000000000..decc5a8573 --- /dev/null +++ b/config/locales/en.yml @@ -0,0 +1,33 @@ +# Files in the config/locales directory are used for internationalization +# and are automatically loaded by Rails. If you want to use locales other +# than English, add the necessary files in this directory. +# +# To use the locales, use `I18n.t`: +# +# I18n.t 'hello' +# +# In views, this is aliased to just `t`: +# +# <%= t('hello') %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# The following keys must be escaped otherwise they will not be retrieved by +# the default I18n backend: +# +# true, false, on, off, yes, no +# +# Instead, surround them with single quotes. +# +# en: +# 'true': 'foo' +# +# To learn more, please read the Rails Internationalization guide +# available at http://guides.rubyonrails.org/i18n.html. + +en: + hello: "Hello world" diff --git a/config/puma.rb b/config/puma.rb new file mode 100644 index 0000000000..1e19380dcb --- /dev/null +++ b/config/puma.rb @@ -0,0 +1,56 @@ +# Puma can serve each request in a thread from an internal thread pool. +# The `threads` method setting takes two numbers: a minimum and maximum. +# Any libraries that use thread pools should be configured to match +# the maximum value specified for Puma. Default is set to 5 threads for minimum +# and maximum; this matches the default thread size of Active Record. +# +threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } +threads threads_count, threads_count + +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. +# +port ENV.fetch("PORT") { 3000 } + +# Specifies the `environment` that Puma will run in. +# +environment ENV.fetch("RAILS_ENV") { "development" } + +# Specifies the number of `workers` to boot in clustered mode. +# Workers are forked webserver processes. If using threads and workers together +# the concurrency of the application would be max `threads` * `workers`. +# Workers do not work on JRuby or Windows (both of which do not support +# processes). +# +# workers ENV.fetch("WEB_CONCURRENCY") { 2 } + +# Use the `preload_app!` method when specifying a `workers` number. +# This directive tells Puma to first boot the application and load code +# before forking the application. This takes advantage of Copy On Write +# process behavior so workers use less memory. If you use this option +# you need to make sure to reconnect any threads in the `on_worker_boot` +# block. +# +# preload_app! + +# If you are preloading your application and using Active Record, it's +# recommended that you close any connections to the database before workers +# are forked to prevent connection leakage. +# +# before_fork do +# ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord) +# end + +# The code in the `on_worker_boot` will be called if you are using +# clustered mode by specifying a number of `workers`. After each worker +# process is booted, this block will be run. If you are using the `preload_app!` +# option, you will want to use this block to reconnect to any threads +# or connections that may have been created at application boot, as Ruby +# cannot share connections between processes. +# +# on_worker_boot do +# ActiveRecord::Base.establish_connection if defined?(ActiveRecord) +# end +# + +# Allow puma to be restarted by `rails restart` command. +plugin :tmp_restart diff --git a/config/routes.rb b/config/routes.rb new file mode 100644 index 0000000000..dbfcc7d9ff --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,26 @@ +Rails.application.routes.draw do + root to: 'products#index' + + resources :products, :except => [:delete] + get '/products/:id/retire', to: 'products#retire', as: 'retire' + resources :merchants, :except => [:delete] + get '/merchants/:id/fulfillment', to: 'merchants#fulfillment', as: 'merchant_fulfillment' + get 'merchants/:id/orders/:order_id', to: 'merchants#show_order', as: 'merchant_order' + patch 'merchants/:id/orders/:order_id', to: 'merchants#ship_orderitems', as: 'merchant_ship' + get 'orders/cart', to: 'orders#show_cart', as: 'show_cart' + patch 'orders/:id/quantity/:product_id', to: 'orders#update_quantity', as: 'update_quantity' + # if users go back from their cart to an updated quantity page, redirect to show cart + get 'orders/:id/thank-you', to: 'orders#confirmation', as: 'order_confirmation' + get 'orders/:id/quantity/:product_id', to: 'orders#show_cart', as: 'show_cart_redirect' + delete 'orders/:id/:product_id', to: 'orders#remove_from_cart', as: 'remove_from_cart' + patch '/orders/:id/add/:product_id', to: 'orders#add_item', as: 'add_order_item' + resources :orders, :except => [:delete] + get "/reviews/:product_id/new", to: "reviews#new", as: "new_review" + resources :reviews, :except => [:delete] + get '/checkout', to: 'orders#checkout_form', as: 'checkout_form' + patch '/checkout', to: 'orders#checkout', as: 'checkout' + get "/auth/:provider/callback", to: "sessions#create", as: "auth_callback" + get 'sessions/create' + get 'sessions/index' + post '/logout', to: 'sessions#logout', as: 'logout' +end diff --git a/config/secrets.yml b/config/secrets.yml new file mode 100644 index 0000000000..8c13f39599 --- /dev/null +++ b/config/secrets.yml @@ -0,0 +1,32 @@ +# Be sure to restart your server when you modify this file. + +# Your secret key is used for verifying the integrity of signed cookies. +# If you change this key, all old signed cookies will become invalid! + +# Make sure the secret is at least 30 characters and all random, +# no regular words or you'll be exposed to dictionary attacks. +# You can use `rails secret` to generate a secure secret key. + +# Make sure the secrets in this file are kept private +# if you're sharing your code publicly. + +# Shared secrets are available across all environments. + +# shared: +# api_key: a1B2c3D4e5F6 + +# Environmental secrets are only available for that specific environment. + +development: + secret_key_base: 62b3e85dd4d617276ede0cc3fe9a150086fdb34822960a1711ec27868f199c02bc81bcbbf2ecda768db54b4fe49055e8fff3e6bee7277ff0da40da410455665a + +test: + secret_key_base: 7c215c8c19e65cac00638dc6deec6441aef8f749a5a5e25cada608e61508a8bc50bfd7377e7814ce8cf86ac613f48204b5f55a6c514de43f80fbb5a638893c9c + +# Do not keep production secrets in the unencrypted secrets file. +# Instead, either read values from the environment. +# Or, use `bin/rails secrets:setup` to configure encrypted secrets +# and move the `production:` environment over there. + +production: + secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> diff --git a/config/spring.rb b/config/spring.rb new file mode 100644 index 0000000000..c9119b40c0 --- /dev/null +++ b/config/spring.rb @@ -0,0 +1,6 @@ +%w( + .ruby-version + .rbenv-vars + tmp/restart.txt + tmp/caching-dev.txt +).each { |path| Spring.watch(path) } diff --git a/db/merchant_seeds.csv b/db/merchant_seeds.csv new file mode 100644 index 0000000000..be122cb77c --- /dev/null +++ b/db/merchant_seeds.csv @@ -0,0 +1,27 @@ +id,username,email,oauth_uid,provider +1,skwiens,skwiens@gmail.com,22713806,github +2,KaylaEcker,KaylajayEcker@gmail.com,15035132,github +3,Retropiaf,sawadogochristiane@gmail.com,24403666,github +4,vsawchuk,vtsawchuk@gmail.com,25917903,github +5,Sappy,Sappy@gmail.com,25917904,github +6,Oaky,Oaky@gmail.com,25917905,github +7,Arbor4Life,Arbor4Life@gmail.com,25917906,github +8,Bonzai,Bonzai@gmail.com,25917907,github +9,Mabel_Maple,Mabel_Maple@gmail.com,25917908,github +10,Tall_living,Tall_living@gmail.com,25917909,github +11,Shrubbin,Shrubbin@gmail.com,25917910,github +12,Beleaf,Beleaf@gmail.com,25917911,github +13,MainsTreem,MainsTreem@gmail.com,25917912,github +14,Illlus-Tree-ous,Illlus-Tree-ous@gmail.com,25917913,github +15,InTREEguing,InTREEguing@gmail.com,25917914,github +16,S-tree-ker,S-tree-ker@gmail.com,25917915,github +17,Living the Treem,Living the Treem@gmail.com,25917916,github +18,Be-Tree,Be-Tree@gmail.com,25917917,github +19,BeLeaf,BeLeaf@gmail.com,25917918,github +20,ManaTree,ManaTree@gmail.com,25917919,github +21,PoeTree writer,PoeTree writer@gmail.com,25917920,github +22,Roots,Roots@gmail.com,25917921,github +23,ACorny,ACorny@gmail.com,25917922,github +24,Stumped,Stumped@gmail.com,25917923,github +25,Ent,Ent@gmail.com,25917924,github +26,Groot,Groot@gmail.com,25917925,github diff --git a/db/migrate/20171017194630_create_products.rb b/db/migrate/20171017194630_create_products.rb new file mode 100644 index 0000000000..164da21e54 --- /dev/null +++ b/db/migrate/20171017194630_create_products.rb @@ -0,0 +1,10 @@ +class CreateProducts < ActiveRecord::Migration[5.1] + def change + create_table :products do |t| + t.string :name + t.integer :price + + t.timestamps + end + end +end diff --git a/db/migrate/20171017203621_product_category.rb b/db/migrate/20171017203621_product_category.rb new file mode 100644 index 0000000000..8114933622 --- /dev/null +++ b/db/migrate/20171017203621_product_category.rb @@ -0,0 +1,5 @@ +class ProductCategory < ActiveRecord::Migration[5.1] + def change + add_column :products, :category, :string + end +end diff --git a/db/migrate/20171017203923_product_quantity.rb b/db/migrate/20171017203923_product_quantity.rb new file mode 100644 index 0000000000..d201b2c5c9 --- /dev/null +++ b/db/migrate/20171017203923_product_quantity.rb @@ -0,0 +1,5 @@ +class ProductQuantity < ActiveRecord::Migration[5.1] + def change + add_column :products, :quantity, :string + end +end diff --git a/db/migrate/20171017205747_create_merchants.rb b/db/migrate/20171017205747_create_merchants.rb new file mode 100644 index 0000000000..64a57c904c --- /dev/null +++ b/db/migrate/20171017205747_create_merchants.rb @@ -0,0 +1,10 @@ +class CreateMerchants < ActiveRecord::Migration[5.1] + def change + create_table :merchants do |t| + t.string :username + t.string :email + + t.timestamps + end + end +end diff --git a/db/migrate/20171017210543_merchant_has_products.rb b/db/migrate/20171017210543_merchant_has_products.rb new file mode 100644 index 0000000000..954ff90065 --- /dev/null +++ b/db/migrate/20171017210543_merchant_has_products.rb @@ -0,0 +1,5 @@ +class MerchantHasProducts < ActiveRecord::Migration[5.1] + def change + add_reference :products, :merchant, foreign_key: true + end +end diff --git a/db/migrate/20171017224721_create_orders.rb b/db/migrate/20171017224721_create_orders.rb new file mode 100644 index 0000000000..a20744ec30 --- /dev/null +++ b/db/migrate/20171017224721_create_orders.rb @@ -0,0 +1,19 @@ +class CreateOrders < ActiveRecord::Migration[5.1] + def change + create_table :orders do |t| + t.string :customer_email + t.string :address1 + t.string :address2 + t.string :city + t.string :state + t.integer :zipcode + t.string :cc_name + t.integer :cc_number + t.string :cc_expiration + t.integer :cc_security + t.integer :billingzip + + t.timestamps + end + end +end diff --git a/db/migrate/20171017225720_orderitem.rb b/db/migrate/20171017225720_orderitem.rb new file mode 100644 index 0000000000..3ec5415039 --- /dev/null +++ b/db/migrate/20171017225720_orderitem.rb @@ -0,0 +1,10 @@ +class Orderitem < ActiveRecord::Migration[5.1] + def change + create_table :orderitems do |t| + t.belongs_to :order, index: true + t.belongs_to :product, index: true + t.integer :quantity + t.timestamps + end + end +end diff --git a/db/migrate/20171017230352_foreignkey.rb b/db/migrate/20171017230352_foreignkey.rb new file mode 100644 index 0000000000..3bb20329c7 --- /dev/null +++ b/db/migrate/20171017230352_foreignkey.rb @@ -0,0 +1,5 @@ +class Foreignkey < ActiveRecord::Migration[5.1] + def change + add_foreign_key :orderitems, :orders + end +end diff --git a/db/migrate/20171017230402_foreignkey2.rb b/db/migrate/20171017230402_foreignkey2.rb new file mode 100644 index 0000000000..e64b250dca --- /dev/null +++ b/db/migrate/20171017230402_foreignkey2.rb @@ -0,0 +1,5 @@ +class Foreignkey2 < ActiveRecord::Migration[5.1] + def change + add_foreign_key :orderitems, :products + end +end diff --git a/db/migrate/20171017230819_create_orderitems.rb b/db/migrate/20171017230819_create_orderitems.rb new file mode 100644 index 0000000000..6e5bf7aac8 --- /dev/null +++ b/db/migrate/20171017230819_create_orderitems.rb @@ -0,0 +1,5 @@ +class CreateOrderitems < ActiveRecord::Migration[5.1] + def change + + end +end diff --git a/db/migrate/20171017231027_update_products_price_to_integer.rb b/db/migrate/20171017231027_update_products_price_to_integer.rb new file mode 100644 index 0000000000..aa2538e4a2 --- /dev/null +++ b/db/migrate/20171017231027_update_products_price_to_integer.rb @@ -0,0 +1,5 @@ +class UpdateProductsPriceToInteger < ActiveRecord::Migration[5.1] + def change + change_column :products, :price, :float + end +end diff --git a/db/migrate/20171018182712_add_status_to_orders.rb b/db/migrate/20171018182712_add_status_to_orders.rb new file mode 100644 index 0000000000..1eb4e979c8 --- /dev/null +++ b/db/migrate/20171018182712_add_status_to_orders.rb @@ -0,0 +1,5 @@ +class AddStatusToOrders < ActiveRecord::Migration[5.1] + def change + add_column :orders, :status, :string + end +end diff --git a/db/migrate/20171018183820_change_cc_number_to_bigint.rb b/db/migrate/20171018183820_change_cc_number_to_bigint.rb new file mode 100644 index 0000000000..681a2e1848 --- /dev/null +++ b/db/migrate/20171018183820_change_cc_number_to_bigint.rb @@ -0,0 +1,5 @@ +class ChangeCcNumberToBigint < ActiveRecord::Migration[5.1] + def change + change_column :orders, :cc_number, :bigint + end +end diff --git a/db/migrate/20171018193825_create_reviews.rb b/db/migrate/20171018193825_create_reviews.rb new file mode 100644 index 0000000000..e675b24539 --- /dev/null +++ b/db/migrate/20171018193825_create_reviews.rb @@ -0,0 +1,10 @@ +class CreateReviews < ActiveRecord::Migration[5.1] + def change + create_table :reviews do |t| + t.integer :rating + t.references :product, foreign_key: true + + t.timestamps + end + end +end diff --git a/db/migrate/20171018204835_merchantsoauth.rb b/db/migrate/20171018204835_merchantsoauth.rb new file mode 100644 index 0000000000..f004524695 --- /dev/null +++ b/db/migrate/20171018204835_merchantsoauth.rb @@ -0,0 +1,6 @@ +class Merchantsoauth < ActiveRecord::Migration[5.1] + def change + add_column :merchants, :uid, :integer, null: false + add_column :merchants, :provider, :string, null: false + end +end diff --git a/db/migrate/20171018210207_oauthnamechange.rb b/db/migrate/20171018210207_oauthnamechange.rb new file mode 100644 index 0000000000..d5c9e84fa1 --- /dev/null +++ b/db/migrate/20171018210207_oauthnamechange.rb @@ -0,0 +1,9 @@ +class Oauthnamechange < ActiveRecord::Migration[5.1] + def change + remove_column :merchants, :uid + remove_column :merchants, :provider + + add_column :merchants, :oauth_uid, :integer, null: false + add_column :merchants, :oauth_provider, :string, null: false + end +end diff --git a/db/migrate/20171018211201_add_review_text_colum.rb b/db/migrate/20171018211201_add_review_text_colum.rb new file mode 100644 index 0000000000..a5e293dd32 --- /dev/null +++ b/db/migrate/20171018211201_add_review_text_colum.rb @@ -0,0 +1,6 @@ +class AddReviewTextColum < ActiveRecord::Migration[5.1] + + def change + add_column :reviews, :review_text, :text + end +end diff --git a/db/migrate/20171018214013_update_product_quantity_from_string_to_int.rb b/db/migrate/20171018214013_update_product_quantity_from_string_to_int.rb new file mode 100644 index 0000000000..2a1c9c29f5 --- /dev/null +++ b/db/migrate/20171018214013_update_product_quantity_from_string_to_int.rb @@ -0,0 +1,5 @@ +class UpdateProductQuantityFromStringToInt < ActiveRecord::Migration[5.1] + def change + change_column :products, :quantity, 'integer USING CAST(products.quantity AS integer)' + end +end diff --git a/db/migrate/20171019161853_add_review_title.rb b/db/migrate/20171019161853_add_review_title.rb new file mode 100644 index 0000000000..ef7d613bb2 --- /dev/null +++ b/db/migrate/20171019161853_add_review_title.rb @@ -0,0 +1,5 @@ +class AddReviewTitle < ActiveRecord::Migration[5.1] + def change + add_column :reviews, :title, :string + end +end diff --git a/db/migrate/20171019172605_categoriest_to_array.rb b/db/migrate/20171019172605_categoriest_to_array.rb new file mode 100644 index 0000000000..235d280ee7 --- /dev/null +++ b/db/migrate/20171019172605_categoriest_to_array.rb @@ -0,0 +1,5 @@ +class CategoriestToArray < ActiveRecord::Migration[5.1] + def change + add_column :products, :categories, :string, array:true, default: [] + end +end diff --git a/db/migrate/20171019173159_remove_category.rb b/db/migrate/20171019173159_remove_category.rb new file mode 100644 index 0000000000..fcf614b601 --- /dev/null +++ b/db/migrate/20171019173159_remove_category.rb @@ -0,0 +1,5 @@ +class RemoveCategory < ActiveRecord::Migration[5.1] + def change + remove_column :products, :category + end +end diff --git a/db/migrate/20171019222601_change_orders_integers_to_string.rb b/db/migrate/20171019222601_change_orders_integers_to_string.rb new file mode 100644 index 0000000000..1ff5d05188 --- /dev/null +++ b/db/migrate/20171019222601_change_orders_integers_to_string.rb @@ -0,0 +1,8 @@ +class ChangeOrdersIntegersToString < ActiveRecord::Migration[5.1] + def change + change_column :orders, :zipcode, :string + change_column :orders, :cc_number, :string + change_column :orders, :cc_security, :string + change_column :orders, :billingzip, :string + end +end diff --git a/db/migrate/20171022161050_description_an_dphot.rb b/db/migrate/20171022161050_description_an_dphot.rb new file mode 100644 index 0000000000..6a2772f084 --- /dev/null +++ b/db/migrate/20171022161050_description_an_dphot.rb @@ -0,0 +1,6 @@ +class DescriptionAnDphot < ActiveRecord::Migration[5.1] + def change + add_column :products, :description, :text + add_column :products, :photo_url, :string + end +end diff --git a/db/migrate/20171022175255_add_purchase_datetime_to_orders.rb b/db/migrate/20171022175255_add_purchase_datetime_to_orders.rb new file mode 100644 index 0000000000..da11aa6169 --- /dev/null +++ b/db/migrate/20171022175255_add_purchase_datetime_to_orders.rb @@ -0,0 +1,5 @@ +class AddPurchaseDatetimeToOrders < ActiveRecord::Migration[5.1] + def change + add_column :orders, :purchase_timestamp, :datetime + end +end diff --git a/db/migrate/20171022175708_rename_orders_purchase_timestamp.rb b/db/migrate/20171022175708_rename_orders_purchase_timestamp.rb new file mode 100644 index 0000000000..4892de1305 --- /dev/null +++ b/db/migrate/20171022175708_rename_orders_purchase_timestamp.rb @@ -0,0 +1,5 @@ +class RenameOrdersPurchaseTimestamp < ActiveRecord::Migration[5.1] + def change + rename_column :orders, :purchase_timestamp, :purchase_datetime + end +end diff --git a/db/migrate/20171023194142_product_status.rb b/db/migrate/20171023194142_product_status.rb new file mode 100644 index 0000000000..331d9178a3 --- /dev/null +++ b/db/migrate/20171023194142_product_status.rb @@ -0,0 +1,5 @@ +class ProductStatus < ActiveRecord::Migration[5.1] + def change + add_column :products, :active, :boolean + end +end diff --git a/db/migrate/20171023195347_active_status.rb b/db/migrate/20171023195347_active_status.rb new file mode 100644 index 0000000000..96e6866475 --- /dev/null +++ b/db/migrate/20171023195347_active_status.rb @@ -0,0 +1,5 @@ +class ActiveStatus < ActiveRecord::Migration[5.1] + def change + rename_column :products, :active, :active_status + end +end diff --git a/db/migrate/20171023200504_product_status_change.rb b/db/migrate/20171023200504_product_status_change.rb new file mode 100644 index 0000000000..6af07debce --- /dev/null +++ b/db/migrate/20171023200504_product_status_change.rb @@ -0,0 +1,6 @@ +class ProductStatusChange < ActiveRecord::Migration[5.1] + def change + remove_column :products, :active_status + add_column :products, :status, :string + end +end diff --git a/db/migrate/20171024041915_shipping_name_order.rb b/db/migrate/20171024041915_shipping_name_order.rb new file mode 100644 index 0000000000..7aceef52f3 --- /dev/null +++ b/db/migrate/20171024041915_shipping_name_order.rb @@ -0,0 +1,5 @@ +class ShippingNameOrder < ActiveRecord::Migration[5.1] + def change + add_column :products, :customer_name, :string + end +end diff --git a/db/migrate/20171024043050_order_name_fix.rb b/db/migrate/20171024043050_order_name_fix.rb new file mode 100644 index 0000000000..fae2c03c8b --- /dev/null +++ b/db/migrate/20171024043050_order_name_fix.rb @@ -0,0 +1,6 @@ +class OrderNameFix < ActiveRecord::Migration[5.1] + def change + remove_column :products, :customer_name + add_column :orders, :customer_name, :string + end +end diff --git a/db/migrate/20171024172616_add_status_to_orderitems.rb b/db/migrate/20171024172616_add_status_to_orderitems.rb new file mode 100644 index 0000000000..0a0adafbf2 --- /dev/null +++ b/db/migrate/20171024172616_add_status_to_orderitems.rb @@ -0,0 +1,5 @@ +class AddStatusToOrderitems < ActiveRecord::Migration[5.1] + def change + add_column :orderitems, :status, :string + end +end diff --git a/db/order_seeds.csv b/db/order_seeds.csv new file mode 100644 index 0000000000..8f5719cd93 --- /dev/null +++ b/db/order_seeds.csv @@ -0,0 +1,11 @@ +id,customer_name,customer_email,address1,address2,city,state,zipcode,cc_name,cc_number,cc_expiration,cc_security,billingzip,status +1,Sappy Susan,christiane@gmail.com,123 A Street,,Arbor,NY,93507,Christiane S,4111283003558320,01/21,791,96535,pending +2,Maple Mable,shaunna@gmail.com,456 B Street,#4,Maple,HI,92509,Shaunna W,4111381187187510,12/18,539,92509,paid +3,Stumped Sam,kayla@gmail.com,1 3rd Ave,,Pine,WA,93032,Kayla E,4111515970319250,11/23,322,96433,complete +4,Arbor Alexandra,victoria@gmail.com,12 8th Ave,#21,Cedar,NY,99690,Victoria S,4111892427248440,05/21,291,99690,cancelled +5,Dee Tree,christ@gmail.com,21 Fake Pl,,Binary,NY,90097,Christ M Tree,4111698564821290,05/22,981,99642,pending +6,Chris(tmas) Tree,dee@gmail.com,13 Real St,Apt E,Lahaina,HI,99858,Dee Tree,4111258502273620,05/23,259,99858,pending +7,Cynthia Tree,cynthia@gmail.com,321 C Ave,,Seattle,WA,92249,Cynthia Tree,4111261136939650,05/24,385,98631,paid +8,Palm Pam,sarah@gmail.com,1923 4th Ave,,New York,NY,90161,Sarah Evergreen,4111702954334590,05/25,941,90161,paid +9,Willow Wendy,alexandra@gmail.com,1230 3rd Ave,Apt 3,New York,NY,96558,Alexandra H,4111941145824450,05/26,382,96558,complete +10,Cherry Blossom,dan@gmail.com,1083 2nd Ave,,Lahaina,HI,98953,Dan R,4111597016882430,05/27,488,93479,complete diff --git a/db/orderitem_seeds.csv b/db/orderitem_seeds.csv new file mode 100644 index 0000000000..69675f5f63 --- /dev/null +++ b/db/orderitem_seeds.csv @@ -0,0 +1,11 @@ +id,order_id,product_id,quantity +1,5,3,1 +2,10,2,5 +3,3,1,2 +4,9,4,3 +5,4,2,4 +6,1,4,2 +7,1,5,5 +8,1,1,5 +9,2,5,3 +10,2,4,5 diff --git a/db/product_seeds.csv b/db/product_seeds.csv new file mode 100644 index 0000000000..475b4c5a8a --- /dev/null +++ b/db/product_seeds.csv @@ -0,0 +1,63 @@ +id,name,price,categories,merchant_id,quantity,status,description,photo_url +1,Heartree,0.97,"{indoor,valentine,love,color,decorative}",1,89,active,"Only God can make a tree - but you can paint one. If you overwork it you become a cloud killer. There's nothing worse than a cloud killer. This is the time to get out all your flustrations, much better than kicking the dog around the house or taking it out on your spouse.",https://images.pexels.com/photos/207962/pexels-photo-207962.jpeg?w=940&h=650&dpr=2&auto=compress&cs=tinysrgb +3,Chemistree,3.28,"{indoor,science}",2,86,active,"Let your heart take you to wherever you want to be. Very easy to work these to death. With something so strong, a little bit can go a long way. It's cold, but it's beautiful. Paint anything you want on the canvas. Create your own world. Be brave. This painting comes right out of your heart. I'm a water fanatic. I love water.",https://i.pinimg.com/736x/ec/f0/c2/ecf0c28e20b1ac071c2e6dc33e7bb5dc--christmas-tree-ideas-merry-christmas.jpg +2,GoatTree,3.49,"{outdoor,animals,accessories}",1,84,active,"A big strong tree needs big strong roots. This is your world, whatever makes you happy you can put in it. Go crazy. Let your heart be your guide. It looks so good, I might as well not stop. Don't fiddle with it all day. Now let's put some happy little clouds in here. We might as well make some Almighty mountains today as well, what the heck. Mix your color marbly don't mix it dead.",https://media.mnn.com/assets/images/2015/03/GoatsInTress02-e-0302.jpg +4,Squirrel,6.65,"{accessories,animals}",7,76,active,You have freedom here. The only guide is your heart. You're the greatest thing that has ever been or ever will be. You're special. You're so very special. When things happen - enjoy them. They're little gifts. Let your imagination be your guide. Absolutely no pressure. You are just a whisper floating across a mountain. We don't need any guidelines or formats. All we need to do is just let it flow right out of us. If what you're doing doesn't make you happy - you're doing the wrong thing.,https://images.pexels.com/photos/162061/squirrel-ulm-garden-summer-162061.jpeg?h=350&dpr=2&auto=compress&cs=tinysrgb +5,puffintree,8.29,"{outdoor,accessories,animals}",4,97,active,"How do you make a round circle with a square knife? That's your challenge for the day. Put it in, leave it alone. If I paint something, I don't want to have to explain what it is. Get tough with it, get strong. We need dark in order to show light. There's nothing wrong with having a tree as a friend. In your imagination you can go anywhere you want.",https://images.pexels.com/photos/618288/pexels-photo-618288.jpeg?h=350&dpr=2&auto=compress&cs=tinysrgb +6,periodic tree,9.39,"{indoor,science}",5,71,active,Maybe he has a little friend that lives right over here. Let's build an almighty mountain. Go out on a limb - that's where the fruit is. Remember how free clouds are. They just lay around in the sky all day long. Anyone can paint. Poor old tree. It's amazing what you can do with a little love in your heart.,https://sciencenotes.org/wp-content/uploads/2014/12/Element-Chemis-tree.png +7,Ent,1.89,"{indoor,outdoor,tv,Lord of the Rings,fantasy }",25,56,active,"Do an almighty painting with us. Van Dyke Brown is a very nice brown, it's almost like a chocolate brown. I started painting as a hobby when I was little. I didn't know I had any talent. I believe talent is just a pursued interest. Anybody can do what I do. Every single thing in the world has its own personality - and it is up to you to make friends with the little rascals. You can spend all day playing with mountains.",https://02varvara.files.wordpress.com/2007/11/treebeard-the-ent-e1269783419659.jpg +8,Baobab,8.09,"{outdoor,giant,tropical}",8,92,active,"Fluff it up a little and hypnotize it. Let's have a nice tree right here. It's almost like something out of a fairytale book. It looks so good, I might as well not stop. Isn't that fantastic? You can just push a little tree out of your brush like that.",https://upload.wikimedia.org/wikipedia/commons/a/a8/Adansonia_grandidieri04.jpg +9,Weeping Willow,5.5,"{indoor,sad}",3,73,active,You can do anything here - the only pre-requisite is that it makes you happy. These little son of a guns hide in your brush and you just have to push them out. Nothing wrong with washing your brush. This is probably the greatest thing to happen in my life - to be able to share this with you. All you have to learn here is how to have fun.,https://i.pinimg.com/736x/9b/00/53/9b0053be9b5ed08a474d07e6e1338a77--weeping-willow-willow-tree.jpg +10,Groot,3.32,"{outer-space,alien,hero,sci-fi,fantasy,movie}",26,88,active,"Maybe there's a happy little waterfall happening over here. You have to make these big decisions. Even trees need a friend. We all need friends. We don't have to be committed. We are just playing here. Now then, let's play. There's not a thing in the world wrong with washing your brush.",https://geekdad.com/wp-content/uploads/2017/05/baby-groot-guardians.jpg +11,Money Tree,1000000,"{green,luxury,bling}",5,2,active,"See there, told you that would be easy. This is a happy place, little squirrels live here and play. We spend so much of our life looking - but never seeing. When you do it your way you can go anywhere you choose.",http://www.salvatoremanzi.com/wp-content/uploads/2013/04/dollar-money-tree.jpg +12,Drive-through,200,{drive-through},6,1,active,"Each highlight must have it's own private shadow. Poor old tree. It's beautiful - and we haven't even done anything to it yet. It's hard to see things when you're too close. Take a step back and look. Water's like me. It's laaazy ... Boy, it always looks for the easiest way to do things If you don't think every day is a good day - try missing a few. You'll see. If you've been in Alaska less than a year you're a Cheechako. Maybe there was an old trapper that lived out here and maybe one day he went to check his beaver traps, and maybe he fell into the river and drowned.",http://blog.davey.com/media/4101/chandeliertree-dreamstimeweb_369x246.jpg +13,Drinking Tree,50.23,"{bottles,beer,frat}",4,42,active,"See there how easy that is. I thought today we would do a happy little picture. Let's make a nice big leafy tree. This is your world, whatever makes you happy you can put in it. Go crazy.",http://ugly-christmas-trees.com/trees/beer-bottle/beer-bottle-tree.jpg +14,Trick or Tree,13.24,"{halloween,spooky,foodie,edible}",7,23,active,"Sometimes you learn more from your mistakes than you do from your masterpieces. Let your heart be your guide. It's a very cold picture, I may have to go get my coat. It’s about to freeze me to death. Son of a gun. Nice little fluffy clouds laying around in the sky being lazy.",https://www.trendytree.com/blog/wp-content/uploads/2011/07/halloween-candy-tree.jpg +15,Mon-Tree-Al,23.45,{canada},9,12,active,"Let's do it again then, what the heck. Anyone can paint. Just let go - and fall like a little waterfall. You don't have to spend all your time thinking about what you're doing, you just let it happen. This is your world. Maybe he has a little friend that lives right over here. Absolutely no pressure. You are just a whisper floating across a mountain.",https://images.fineartamerica.com/images-medium-large-5/canadian-flag-on-isolated-tree-saurabh-nande.jpg +16,Natural Binary Tree,1010.1,"{1100101,science,Ada}",1,110,active,A little happy sunlight shining through there. We can always carry this a step further. There's really no end to this. You gotta think like a tree. See how easy it is to create a little tree right in your world.,https://qph.ec.quoracdn.net/main-qimg-cd325accc5d997fb0e16f747003a8c5b +17,Treeo,3.33,{trio},3,3,active,"Buy one, get trees! You have to make almighty decisions when you're the creator. Trees grow in all kinds of ways. They're not all perfectly straight. Not every limb is perfect. Happy painting, God bless. These trees are so much fun. I get started on them and I have a hard time stopping.",https://st.hzcdn.com/simgs/96e1673505f09636_4-7380/traditional-christmas-trees.jpg +18,Pastree Chef,45.67,"{poster,foodie}",2,45,active,"Let your imagination be your guide. Don't be bashful drop me a line. All you have to do is let your imagination go wild. If these lines aren't straight, your water's going to run right out of your painting and get your floor wet. But we're not there yet, so we don't need to worry about it. That's the way I look when I get home late; black and blue.",https://i.pinimg.com/originals/4d/98/f2/4d98f2c5e770187027ceb68b428bccaa.jpg +19,Geome-tree,23.9,"{green,science,geometry}",8,45,active,"Now it's beginning to make a little sense. You don't have to be crazy to do this but it does help. Any little thing can be your friend if you let it be. Let's get crazy. How do you make a round circle with a square knife? That's your challenge for the day. Let's make some happy little clouds in our world. Trees get lonely too, so we'll give him a little friend.",https://pics.me.me/geometree-cutting-hedge-humor-25131311.png +20,Poul-tree,87.23,"{poster,animals,poultry,accessories}",1,11,active,"For the lack of a better word I call them hangy downs. We tell people sometimes: we're like drug dealers, come into town and get everybody absolutely addicted to painting. It doesn't take much to get you addicted. Let's build some happy little clouds up here. But they're very easily killed. Clouds are delicate. We don't make mistakes we just have happy little accidents. And right there you got an almighty cloud. This is the time to get out all your flustrations, much better than kicking the dog around the house or taking it out on your spouse. Work that paint.",https://www.artmajeur.com/medias/mini/o/u/outonalimb/artwork/6919492_chixtree.jpg +21,Circuit-tree,39.72,"{indoor,science,electronics}",8,56,active,"That's what painting is all about. It should make you feel good when you paint. There are no limits in this world. The man who does the best job is the one who is happy at his job. Nature is so fantastic, enjoy it. Let it make you happy.",https://img00.deviantart.net/6e97/i/2012/036/0/9/circuitree_final_by_taytay1000-d4orkuv.jpg +22,Mili-tree,1.13,"{logo,minimalist}",2,22,active,I'm gonna start with a little Alizarin crimson and a touch of Prussian blue We don't have anything but happy trees here. Here's something that's fun. You can create the world you want to see and be a part of. You have that power. Isn't that fantastic?,https://i.pinimg.com/736x/91/ea/90/91ea909ee4ca1b12c3486fd080bd5fd5.jpg +23,Punny Tree,21.97,"{poster,funny}",3,91,active,"Automatically, all of these beautiful, beautiful things will happen. A tree cannot be straight if it has a crooked trunk. If it's not what you want - stop and change it. Don't just keep going and expect it will get better. We must be quiet, soft and gentle. And just raise cain. Of course he's a happy little stone, cause we don't have any other kind.",http://punpedia.org/wp-content/uploads/2016/09/Thewoodwoodisahardfibrousstructuraltissuefound_59883b_5035333.jpg +24,Junk in the Trunk,21.83,"{poster,funny,sexy}",6,62,active,"Get away from those little Christmas tree things we used to make in school. Now we don't want him to get lonely, so we'll give him a little friend. You can do anything your heart can imagine. You've got to learn to fight the temptation to resist these things. Just let them happen.",https://imgix.ranker.com/user_node_img/50058/1001157343/original/k-photo-u1?w=650&q=50&fm=jpg&fit=crop&crop=faces +25,Oaky the Happy Tree,71.3,"{outdoor,luxury,giant}",5,1,active,"Fluff it up a little and hypnotize it. Let's have a nice tree right here. It's almost like something out of a fairytale book. It looks so good, I might as well not stop. Isn't that fantastic? You can just push a little tree out of your brush like that.",https://www.fast-growing-trees.com/images/P/Sawtooth-Oak_450_a.jpg +26,Acacia,22.6,"{acacia,giant,outdoor}",22,23,active,"How to paint. That's easy. What to paint. That's much harder. Let your heart take you to wherever you want to be. If what you're doing doesn't make you happy - you're doing the wrong thing. If we're gonna walk though the woods, we need a little path. In this world, everything can be happy. There's nothing wrong with having a tree as a friend.",https://www.collinsdictionary.com/images/thumb/acaciatree_94409617_250.jpg +27,Wisteria,34.12,"{decorative,purple,color}",11,35,active,You have freedom here. The only guide is your heart. All kinds of happy little splashes. You can do anything here - the only pre-requisite is that it makes you happy. You can't have light without dark. You can't know happiness unless you've known sorrow.,https://usercontent1.hubstatic.com/8316510_f520.jpg +28,Almond Tree,22.15,"{edible,decorative,outdoor}",12,75,active,"A big strong tree needs big strong roots. Any little thing can be your friend if you let it be. Every time you practice, you learn more. Work that paint.",http://www.westernfarmpress.com/sites/westernfarmpress.com/files/styles/article_featured_standard/public/Almond%20Orchard%20in%20Bloom1_2.jpg?itok=WdYk8RVm +29,Cherry Tree,11.94,"{edible,outdoor}",13,54,active,That's a crooked tree. We'll send him to Washington. A happy cloud. Let's have a happy little tree in here. This is truly an almighty mountain.,https://maxpull-tlu7l6lqiu.stackpathdns.com/wp-content/uploads/2015/05/cherry-trees.jpg +30,Organic Apple Tree,22.73,"{edible,oudoor}",4,42,active,"You've got to learn to fight the temptation to resist these things. Just let them happen. The only thing worse than yellow snow is green snow. It looks so good, I might as well not stop.",https://www.rodalesorganiclife.com/sites/rodalesorganiclife.com/files/styles/listicle_main_custom_user_phone_1x/public/appletree_catalin_petolea.jpg?itok=nU44qP-S×tamp=1474999925 +31,Pear Tree,22.24,"{edible,giant,outdoor}",15,90,active,"Automatically, all of these beautiful, beautiful things will happen. Clouds are free they come and go as they please. You can create beautiful things - but you have to see them in your mind first. Use what happens naturally, don't fight it.",https://maxpull-tlu7l6lqiu.stackpathdns.com/wp-content/uploads/2015/08/old-pear-tree.jpg +32,Lil' tree,15,"{indoor,mini,bonsai,japanese}",8,7,active,"Painting should do one thing. It should put happiness in your heart. If you overwork it you become a cloud killer. There's nothing worse than a cloud killer. Nature is so fantastic, enjoy it. Let it make you happy. I'm going to mix up a little color. We’ll use Van Dyke Brown, Permanent Red, and a little bit of Prussian Blue. If you don't think every day is a good day - try missing a few. You'll see.",https://images-na.ssl-images-amazon.com/images/I/51fIoHW5eJL._SY355_.jpg +33,Lil' giant,15,"{indoor,mini,bonsai,japanese}",8,63,active,"If we're gonna walk though the woods, we need a little path. Paint anything you want on the canvas. Create your own world. Now then, let's play. Don't be afraid to make these big decisions. Once you start, they sort of just make themselves.",https://vignette.wikia.nocookie.net/plantsvszombies/images/8/83/08-juniper-bonsai-blasco-paz.jpg/revision/latest?cb=20141013031553 +34,Lil' purple,15,"{indoor,mini,bonsai,japanese,purple,color}",8,3,active,"Just let this happen. We just let this flow right out of our minds. You can create beautiful things - but you have to see them in your mind first. Every time you practice, you learn more. And that's when it becomes fun - you don't have to spend your time thinking about what's happening - you just let it happen. Use what you see, don't plan it. See. We take the corner of the brush and let it play back-and-forth.",https://static.boredpanda.com/blog/wp-content/uploads/2016/04/amazing-bonsai-trees-2-5710e785cd3a3__700.jpg +35,Lil' Marple,15,"{indoor,canada,marple,mini,bonsai,japanese,red,color}",8,47,active,"We'll do another happy little painting. If you do too much it's going to lose its effectiveness. We'll put some happy little leaves here and there. Automatically, all of these beautiful, beautiful things will happen. This is unplanned it really just happens.",https://static.boredpanda.com/blog/wp-content/uploads/2016/04/amazing-bonsai-trees-4-5710e792c2477__700.jpg +36,Lil' apple,25,"{indoor,apple,mini,bonsai,japanese,edible",8,181,active,"And right there you got an almighty cloud. I'll go over the colors one more time that we use: Titanium white, Thalo green, Prussian blue, Van Dyke brown, Alizarin crimson, Sap green, Cad yellow, and Permanent red. Let's do it again then, what the heck. All you need to paint is a few tools, a little instruction, and a vision in your mind. This is the way you take out your flustrations. Don't hurry. Take your time and enjoy.",https://static.boredpanda.com/blog/wp-content/uploads/2016/04/amazing-bonsai-trees-12-5710ed059e7f7__700.jpg +37,Winter is coming,685,"{christmas,winter,decoration}",1,14,active,"Now, we're going to fluff this cloud. In painting, you have unlimited power. You have the ability to move mountains. You can bend rivers. But when I get home, the only thing I have power over is the garbage. Let's put some happy trees and bushes back in here.",https://www.bonsaiempire.com/images/articles-blog/old-articles/6-bag-end-bonsai-winter-dec-2009.jpeg +38,S'more Trees Pleaz,803,"{toy,cute,foodie,smores}",15,102,active,"You want your tree to have some character. Make it special. It's cold, but it's beautiful. Everyone needs a friend. Friends are the most valuable things in the world. You can't make a mistake. Anything that happens you can learn to use - and make something beautiful out of it. Water's like me. It's laaazy ... Boy, it always looks for the easiest way to do things Let your heart take you to wherever you want to be.",https://images.landofnod.com/is/image/LandOfNod/Toy_Smore_Campfire_Set_243388/$web_product_hero$&/130831065653/the-smore-the-merrier-campfire-set.jpg +39,Loggy-Log,374,"{log,toy,decoration}",20,31,active,"You're the greatest thing that has ever been or ever will be. You're special. You're so very special. There is no right or wrong - as long as it makes you happy and doesn't hurt anyone. Now we don't want him to get lonely, so we'll give him a little friend. Work on one thing at a time. Don't get carried away - we have plenty of time. Only think about one thing at a time. Don't get greedy. A thin paint will stick to a thick paint.",https://images.landofnod.com/is/image/LandOfNod/Imaginary_Log_Seat/$web_product_hero$&/160111085029/log-seat.jpg +40,Wee Tree,466,"{christmas,winter,cute,accessories}",1,92,active,"When you buy that first tube of paint it gives you an artist license. Now let's put some happy little clouds in here. There are no mistakes. You can fix anything that happens. There's nothing wrong with having a tree as a friend. We'll put some happy little leaves here and there. Of course he's a happy little stone, cause we don't have any other kind.",https://images.landofnod.com/is/image/LandOfNod/Wee_Tree_Traditional_Set_RS/$web_product_hero$&/170928091507/wee-christmas-tree-set.jpg +41,Sloth,786,"{sloth,christmas,accessories,decoration}",3,4,active,"Take it slow this Christmas. The more we do this - the more it will do good things to our heart. See. We take the corner of the brush and let it play back-and-forth. Trees get lonely too, so we'll give him a little friend.",https://images.landofnod.com/is/image/LandOfNod/Ornament_Colorful_Sloth_Silo/$web_product_hero$&/170726081612/Ornament_Colorful_Sloth_Silo.jpg +42,Hot tree,416,"{sad,creepy,scary}",1,94,active,Everyone is going to see things differently - and that's the way it should be. Let that brush dance around there and play. A beautiful little sunset. You can't have light without dark. You can't know happiness unless you've known sorrow. If you don't think every day is a good day - try missing a few. You'll see. We don't have anything but happy trees here.,https://encrypted-tbn1.gstatic.com/shopping?q=tbn:ANd9GcSZr5Fc_UVzqRN5cCv0biga4IzIuCIwnKZ50OGxVE4FYeu4r-XgenhVJwfJY0k&usqp=CAY +43,Outch,454,"{sad,creepy,scary}",4,107,active,"Only think about one thing at a time. Don't get greedy. Absolutely no pressure. You are just a whisper floating across a mountain. Nature is so fantastic, enjoy it. Let it make you happy.","https://images.hayneedle.com/mgen/master:ALZ1174.jpg?is=555,555,0xffffff" +44,Klassy Tree,795,"{christmas,bling,winter,modern,minimalist,luxury,hypoallergenic}",14,110,active,"Nice little fluffy clouds laying around in the sky being lazy. Clouds are free. They just float around the sky all day and have fun. We wash our brush with odorless thinner. Use what you see, don't plan it. Water's like me. It's laaazy ... Boy, it always looks for the easiest way to do things And I know you're saying, 'Oh Bob, you've done it this time.' And you may be right.","https://hips.hearstapps.com/hmg-prod.s3.amazonaws.com/images/modern-christmas-trees-15-1504113761.jpg?crop=1xw:1xh;center,top&resize=640:*" +45,Feather Christmas,853,"{christmas,winter,luxury}",2,136,active,"That's a son of a gun of a cloud. I'll go over the colors one more time that we use: Titanium white, Thalo green, Prussian blue, Van Dyke brown, Alizarin crimson, Sap green, Cad yellow, and Permanent red. Maybe he has a little friend that lives right over here.","https://hips.hearstapps.com/hmg-prod.s3.amazonaws.com/images/modern-christmas-trees-2-1504113759.jpg?crop=1xw:1xh;center,top&resize=480:*" +46,Christmas Spirit,413,"{christmas,winter,luxury,modern}",21,48,active,"If you hate Christmas and everything-Christmas but still have to have a Christmas tree, this is your tree. There are no limits in this world. Let's make a happy little mountain now. Anything you want to do you can do here. Don't be bashful drop me a line. This present moment is perfect simply due to the fact you're experiencing it. Just a happy little shadow that lives in there.","https://hips.hearstapps.com/hmg-prod.s3.amazonaws.com/images/modern-christmas-trees-18-1504114710.jpg?crop=0.710xw:0.812xh;0.140xw,0.0952xh&resize=480:*" +47,Fairy Tree,673,"{christmas,winter}",3,180,active,"When things happen - enjoy them. They're little gifts. You can't make a mistake. Anything that happens you can learn to use - and make something beautiful out of it. Talk to trees, look at the birds. Whatever it takes. Even trees need a friend. We all need friends. As trees get older they lose their chlorophyll. Nice little fluffy clouds laying around in the sky being lazy.","https://hips.hearstapps.com/hmg-prod.s3.amazonaws.com/images/modern-chirstmas-trees-10-1504116650.jpg?crop=1xw:1xh;center,top&resize=640:*" +48,Hypoallergenic Tree,310,"{christmas,winter,modern,minimalist,hypoallergenic}",21,181,active,"Don't kill all your dark areas - you need them to show the light. In this world, everything can be happy. If these lines aren't straight, your water's going to run right out of your painting and get your floor wet. Now we don't want him to get lonely, so we'll give him a little friend. Just let your mind wander and enjoy. This should make you happy. You can do anything here. So don't worry about it.","https://hips.hearstapps.com/hmg-prod.s3.amazonaws.com/images/modern-christmas-trees-1-1504113760.jpg?crop=1xw:1xh;center,top&resize=480:*" +49,Plain Christmas,867,"{christmas,winter}",3,161,active,God gave you this gift of imagination. Use it. Be careful. You can always add more - but you can't take it away. This is truly an almighty mountain.,http://www.treetopia.com/v/vspfiles/photos/Oh_Christmas_Tree-2.jpg +50,Proud Tree,630,"{christmas,winter,gaypride}",12,82,active,Maybe he has a little friend that lives right over here. We want to use a lot pressure while using no pressure at all. How to paint. That's easy. What to paint. That's much harder. We have no limits to our world. We're only limited by our imagination. Have fun with it.,http://www.treetopia.com/v/vspfiles/photos/rainbow-christmas-tree-2.jpg +51,Loglady,926,"{tv,Twin Peaks,creepy}",2,34,active,Let's have a little bit of fun today. This present moment is perfect simply due to the fact you're experiencing it. We'll make some happy little bushes here.,http://vignette3.wikia.nocookie.net/twinpeaks/images/6/68/Logladyreplacement.jpg/revision/latest/scale-to-width-down/2000?cb=20160906170235 +52,Cattree,533,"{cats,indoor,luxury,decoration,modern,minimalist}",2,176,active,"This is unplanned it really just happens. I guess that would be considered a UFO. A big cotton ball in the sky. Those great big fluffy clouds. Trees live in your fan brush, but you have to scare them out. You can do it.",https://i.pinimg.com/originals/96/2b/8b/962b8b6aa64338afeaa4cba3c17bbf0a.jpg +53,Big-O-Tree,949,"{bigotry,funny}",11,6,active,"This is the time to get out all your flustrations, much better than kicking the dog around the house or taking it out on your spouse. Nothing wrong with washing your brush. Now we can begin working on lots of happy little things. I really believe that if you practice enough you could paint the 'Mona Lisa' with a two-inch brush. Isn't that fantastic? You can just push a little tree out of your brush like that. There are no mistakes. You can fix anything that happens.",https://rlv.zcache.com/bigot_tree_poster-r4d987211bb8248b5a5f74c3f4ce76df2_wv3_8byvr_540.jpg +54,Ananian Family,330,{family tree},1,48,active,"Buy one tree, get a whole new family. The Ananians, are warm, welcoming and can't wait to meet their new family member! When you buy that first tube of paint it gives you an artist license. Just beat the devil out of it. Happy painting, God bless. If you didn't have baby clouds, you wouldn't have big clouds. We're trying to teach you a technique here and how to use it. You can create beautiful things - but you have to see them in your mind first.",http://www.hollingsworthassociates.com/images/Hollingsworth/FamilyTree.jpg +55,Modest Tree House,587,"{outdoor,squatters,accessories}",25,145,active,"In your world you can create anything you desire. Just go back and put one little more happy tree in there. Just make a decision and let it go. Once you learn the technique, ohhh! Turn you loose on the world; you become a tiger.",http://www.ananian.com/_/rsrc/1242347437547/Home/family-tree/family-tree.png +56,Tree Resort,951,"{outdoor,squatters,luxury,accessories}",21,162,active,"Son of a gun. It's a super day, so why not make a beautiful sky? This is truly an almighty mountain. And right there you got an almighty cloud. It is a lot of fun. For the lack of a better word I call them hangy downs.",https://i.pinimg.com/736x/c6/90/ee/c690eef93fb24c959104bcf50b7f2ce6--guesthouse-treehouses.jpg +57,Modern Tree House,48,"{outdoor,squatters,minimalist,accessories,modern,luxury}",4,39,active,"We can always carry this a step further. There's really no end to this. You better get your coat out, this is going to be a cold painting. Life is too short to be alone, too precious. Share it with a friend. That is when you can experience true joy, when you have no fear. Now we don't want him to get lonely, so we'll give him a little friend.",https://images.adsttc.com/media/images/5947/7fd4/b22e/383a/5900/0178/newsletter/Malan_Vorster_Treehouse_011_Adam_Letch.jpg?1497857964 +58,Cabin in a tree,419,"{outdoor,squatters,accessories}",2,200,active,Volunteering your time; it pays you and your whole community fantastic dividends. These little son of a guns hide in your brush and you just have to push them out. Let all these little things happen. Don't fight them. Learn to use them. You need the dark in order to show the light.,http://www.skamaniacoves.com/images/gallery-treehouse/01-treehouse.jpg +59,Vacation nest,998,"{outdoor,squatters,accessories,luxury,nest}",17,112,active,"Just take out whatever you don't want. It'll change your entire perspective. Just pretend you are a whisper floating across a mountain. All you have to learn here is how to have fun. If you hypnotize it, it will go away. You're meant to have fun in life.",https://cdn-jpg1.theactivetimes.com/sites/default/files/6-playa%20viva-fb.jpg +60,Round nest,669,"{outdoor,squatters,accessories,nest}",3,68,active,"Learn when to stop. You have to make almighty decisions when you're the creator. Use what happens naturally, don't fight it. Talk to trees, look at the birds. Whatever it takes. Those great big fluffy clouds.",https://www.canopyandstars.co.uk/var/self_catering_site/storage/images/canopy-and-stars/britain/england/cornwall/broom-park-farm/lost-meadow-treepod/lost-meadow-tree-tent-gallery/lost-meadow-treehouse-cornwall-treehouse/178925163-5-eng-GB/lost-meadow-treehouse-cornwall-treehouse.jpg +61,Office nest,65,"{indoor,nest}",18,86,active,"In this world, everything can be happy. Paint anything you want on the canvas. Create your own world. Don't fiddle with it all day.",http://smallbusiness.com/wp-content/uploads/2014/03/wieden-kennedy-nest.jpg +62,Ada Tree,35000,"{Ada,non-profit}",4,47,active,"Ada Developers Academy is designed to increase the number of underrepresented skilled software developers while redefining and reinventing the way we prepare individuals to be successful in information technology careers. With an intensive, inclusive, and positive learning community, we aim to directly address both the information technology labor shortage in Washington and the lack of diverse experience, thought, and background in the software industry.",https://img.evbuc.com/https%3A%2F%2Fcdn.evbuc.com%2Fimages%2F33790776%2F146695324165%2F1%2Foriginal.jpg?w=1000&rect=248%2C0%2C3660%2C1830&s=aae9780810c5228229ead00db945f2b2 diff --git a/db/review_seeds.csv b/db/review_seeds.csv new file mode 100644 index 0000000000..ef7b1a72df --- /dev/null +++ b/db/review_seeds.csv @@ -0,0 +1,107 @@ +id,product_id,review_text,review_title,rating +1,23,Great Song by the Beatles. Beautiful love song. George Harrison wrote this song. Beautifully inspired & written...,Something by the Beatles,5 +2,47,Great song!,Five Stars,5 +3,56,love it,Five Stars,5 +4,19,"Betiful song, learning to play it with my ukulele.",George Harrison deserves more credit,5 +5,3,Something is one of the most romantic songs ever written. His songs are some of the best in the Beatles library of music.,Review The Hidden Life of Trees Peter Wohlleben,2 +6,36,Who dont love this song !,Presenting Trees as follow sentient purposeful beings very well done,1 +7,25,AOK,The excellent and the execrable.,3 +8,53,A favorite of mine,I Think That I Shall Never See...,5 +9,43,"The Hidden Life of Trees” is an amazing book presenting trees as sentient, purposeful beings living in dynamic relationship with each other. This is a new aspect for most of us, but apparently has been part of the secret knowledge of foresters since the early 1990’s. Trees, have a sense of time, have memories, taste, smell, feel, explore, see, and hear, but not like we do. Trees even move, from generation to generation just not as individuals. Trees live on a much slower time platform than we do. This single fact has hidden the true life of the trees from us.","Loved the book. Live on a tree-filled island in the summer and have always ""felt"" the companionship of the trees but thought I was crazy. Not any more. Bought it for my four children because they will ""know"" those same trees too.",5 +10,52,"“The Hidden Life of Trees” is carefully and well presented with humor, with gentleness, with compassion, with joy, even with love. The book is not a scientific, heavy fact laden tome. It is a very readable presentation of the last two decades of research into the lives of our follow beings on Earth, the Trees. The author is a German forester, environmentalist who obviously cares very much for his topic of choice.","Misleading ""Facts""",3 +11,53,"Hate dog meow stretch, ears back wide eyed and present belly, scratch hand when stroked cats are fats i like to pets them they like to meow back. Lounge in doorway scratch the postman wake up lick paw wake up owner meow meow.","""...the giraffes were feeding on umbrella thorn acacia, and the trees didn't like this one bit. It took the acacias mere minutes to start pumping toxic substances into their leaves to rid themselves of the large herbivores. The giraffes got the message and moved on to other trees... They walked right by a few trees and and resumed their meal only when they had moved 100 yards away. The acacia trees that were being eaten had given off a warning gas that signaled to neighboring trees... right away, all the forewarned trees also pumped toxins into their leaves... The giraffes were wise to this game and therefore moved farther away to a part of the savannah where they could find trees oblivious to what was going on."" +? +",5 +12,45,"Chew foot sleep nap, yet pet right here, no not there, here, no fool, right here that other cat smells funny you should really give me all the treats because i smell the best and omg you finally got the right spot and i love you right now or purr for no reason but peer out window, chatter at birds, lure them to mouth, and meowwww for meow meow, i tell my human.",Plays league of legends,2 +13,37,"Toy mouse squeak roll over. I just saw other cats inside the house and nobody ask me before using my litter box sit in window and stare oooh, a bird, yum cat mojo , yet pee in human's bed until he cleans the litter box for if it fits, i sits. ",Doggo ipsum pupper ur givin me a spook.,3 +14,11,Get video posted to internet for chasing red dot i like big cats and i can not lie so kitty loves pigs or purr while eating pelt around the house and up and down stairs chasing phantoms.,Pupper heckin good boys and girls,5 +15,1,"Chase imaginary bugs chase ball of string or purr as loud as possible, be the most annoying cat that you can, and, knock everything off the table lick butt and make a weird face lie in the sink all day, mew, yet put butt in owner's face. Chase mice meow use lap as chair kitty scratches couch bad kitty.",Most angery pupper ,1 +16,18,"Pounce on unsuspecting person open the door, let me out, let me out, let me-out, let me-aow, let meaow, meaow! for walk on car leaving trail of paw prints on hood and windshield. Run in circles. Wake up human for food at 4am eat all the power cords for mrow so eat from dog's foodyet stare at ceiling.",Five Stars,5 +17,50,Fall asleep on the washing machine toy mouse squeak roll over or push your water glass on the floor so have a lot of grump in yourself because you can't forget to be grumpy and not be like king grumpy cat so meowing non stop for food eat prawns daintily with a claw then lick paws clean wash down prawns with a lap of carnation milk then retire to the warmest spot on the couch to claw at the fabric before taking a catnap.,Awesome! This book will change your view ,5 +18,24,"Annoy owner until he gives you food say meow repeatedly until belly rubs, feels good stare at guinea pigsstare at ceiling light. Demand to be let outside at once, and expect owner to wait for me as i think about it.",He does so in a very engaging fashion ,4 +19,23,I like big cats and i can not lie. Purr while eating mrow hack up furballsand kitty scratches couch bad kitty.,Five Stars,5 +20,58,Sleep on keyboard destroy couchfind empty spot in cupboard and sleep all day. Always hungry chew on cable and kitten is playing with dead mouse.,A revelation and that book is a must for all ...,5 +21,27,"Throwup on your pillow attack the dog then pretend like nothing happened claws in your leg so scratch the postman wake up lick paw wake up owner meow meow. Unwrap toilet paper steal the warm chair right after you get up slap kitten brother with paw or milk the cow, yet jump five feet high and sideways when a shadow moves. ","Great book, new copy ripped",1 +22,33,"Cats go for world domination get video posted to internet for chasing red dot head nudges meow all night having their mate disturbing sleeping humans so warm up laptop with butt lick butt fart rainbows until owner yells pee in litter box hiss at cats or i like big cats and i can not lie. Man running from cops stops to pet cats, goes to jail that box?",Life as Trees Know It,4 +23,4," i can fit in that box plays league of legends but open the door, let me out, let me out, let me-out, let me-aow, let meaow, meaow! ",Awesome little tree,1 +24,53,"for purr have a lot of grump in yourself because you can't forget to be grumpy and not be like king grumpy cati just saw other cats inside the house and nobody ask me before using my litter box. Instead of drinking water from the cat bowl, make sure to steal water from the toilet catch mouse and gave it as a present kitty kitty thug cat leave fur on owners clothes and intently stare at the same spot.",Two Stars,2 +25,48,Spit up on light gray carpet instead of adjacent linoleumscratch the postman wake up lick paw wake up owner meow meowlick the other cats but push your water glass on the floor lie in the sink all day scratch the postman wake up lick paw wake up owner meow meow yet wake up human for food at 4am.,Exactly right,5 +26,8,"Milk the cow scratch at the door then walk away. Intently stare at the same spot ignore the squirrels, you'll never catch them anyway yet chase ball of string, or chase mice annoy owner until he gives you food say meow repeatedly until belly rubs, feels good poop in a handbag look delicious and drink the soapy mopping up water then puke giant foamy fur-balls.",Amazing bonsái!,5 +27,38,"Russian blue ignore the squirrels, you'll never catch them anyway always hungry",It would be great if it came with basic directions,3 +28,5,Wake up wander around the house making large amounts of noise jump on top of your human's bed,Shipped to Hawaii just fine,3 +29,53,Lick plastic bags. Bathe private parts with tongue then lick owner's face paw at beetle and eat it before it gets away lick the plastic bag.,looked just like the picture with the pond and fisherman,5 +30,30,Chase red laser dot paw at beetle and eat it before it gets away for i am the best yet chase the pig around the house.,Don't be fooled,2 +31,46,"Pee in human's bed until he cleans the litter box annoy owner until he gives you food say meow repeatedly until belly rubs, feels good but chase dog then run away stick butt in face sit and stare and purr licks your face.",Two Stars,2 +32,11,"Mesmerizing birds have secret plans cat mojo touch water with paw then recoil in horror or need to chase tail, yet play riveting piece on synthesizer keyboard meowing non stop for food.",Beautifully Presented and High Quality!,5 +33,34,"Thinking longingly about tuna brine fall over dead (not really but gets sypathy), and my slave human didn't give me any food so i pooped on the floor be a nyan cat, feel great about it, be annoying 24/7 poop rainbows in litter box all day.",Even better than expected!,4 +34,28,Unwrap toilet paper claws in your leg dream about hunting birds purr when being pet rub face on owner for lick human with sandpaper tongue. ,Five Stars,5 +35,22,Lie on your belly and purr when you are asleep.,Tree itself is doing great month later,4 +36,46,"Retro hell of put a bird on it post-ironic cloud bread ramps. Pickled gluten-free retro, shoreditch hot chicken chambray fingerstache pabst VHS. Subway tile snackwave williamsburg, yr gochujang marfa crucifix.",One Star,1 +37,41,Hell of hot chicken single-origin coffee drinking vinegar.,It worked!,4 +38,20,"Fashion axe waistcoat shoreditch, viral truffaut swag irony hammock chia ugh ethical blue bottle kale chips.",Everything a Novice Needs Included,3 +39,21,Af snackwave pug marfa umami. Skateboard put a bird on it truffaut shoreditch shaman vape hell of man bun bitters wayfarers pok pok.,It's dead.,1 +40,28,"Ramps tousled pickled blog actually hot chicken, lumbersexual shabby chic snackwave paleo wolf affogato freegan.","Different than the picture, excellent effort put into presentation!",3 +41,58,"Wayfarers gochujang austin, art party glossier craft beer mixtape. Next level man bun salvia XOXO. Ennui pop-up iPhone offal.",Perfect tree,5 +42,4,"Echo park ugh master cleanse, pok pok ethical next level viral copper mug mlkshk food truck occupy lo-fi williamsburg flexitarian.",Tree died.,1 +43,34,Pop-up keytar sustainable edison bulb lo-fi health goth farm-to-table.,Still Alive! :),5 +44,23,Ugh sartorial kogi put a bird on it whatever kitsch selvage biodiesel.,"I don't know if it's me or the tree, ...",3 +45,4,Pop-up palo santo franzen stumptown hell of health goth.,Disapointing,1 +46,60,"Try-hard distillery wolf YOLO chartreuse lyft blog af forage normcore. Sriracha venmo stumptown cornhole aesthetic next level gluten-free, af pok pok gentrify small batch kale chips DIY.","I don't know if it's me or the tree, ...",2 +47,40,"Cardigan thundercats enamel pin shaman swag YOLO lyft master cleanse hashtag yr poke deep v. Man bun offal fashion axe, waistcoat palo santo wayfarers chartreuse air plant.",GREAT quality!!!!,5 +48,15,"Pug lo-fi activated charcoal gentrify, chartreuse shaman brunch vice keffiyeh taiyaki photo booth enamel pin. ",Lovely plant,5 +49,60,"Cardigan pinterest locavore, tumeric wolf offal meh. Swag meditation hella hot chicken XOXO asymmetrical seitan blog forage migas glossier viral tattooed celiac man braid.",Greenbox is Amazing!,4 +50,10,"Cold-pressed twee ramps tofu gastropub artisan, enamel pin raclette. Edison bulb YOLO hammock, cardigan fixie meh synth dreamcatcher brooklyn.","The tree is fine, the pot was awful!",1 +51,7,Swag brunch cloud bread hoodie jean shorts,Great! Just be ready to wait...,5 +52,32,Cray leggings keytar,"Deceiving Picture, Not Worth Buying",1 +53,12, Chicharrones slow-carb!,So cute!,5 +54,14,PBR&B hell of truffaut,So far so good!,4 +55,61,"Tumblr unicorn vinyl slow-carb helvetica, 8-bit retro pug.",Love it!,5 +56,55,Kombucha coloring book health goth shabby chic you probably haven't heard of them bespoke pop-up man bun whatever organic locavore knausgaard asymmetrical yuccie.,Shipping was fantastic. The tree was well packaged.,5 +57,11,1 narwhal,I am SO in love with my purchase,5 +58,6,Unicorn cornhole copper mug,Mileage will vary,3 +59,48,Small batch,VERY DISSAPPOINTED,2 +60,59,Taxidermy vice aesthetic master,Much ruin diet such treat fat boi puggorino,5 +61,37,I have ever seen fat boi heckin good boys and girls doge shooberino,Wrinkler heckin good boys,5 +62,25,"You are doin me a concern shoober lotsa pats very hand that feed shibe dat tungg tho pats, corgo sub woofer much ruin diet.",I would have settled for something that looked one quarter as good as the image,1 +63,48, fat boi boof wow such tempt heckin angery woofer blop the neighborhood pupper much ruin diet.,Borking doggo long doggo pupper,2 +64,41,"Wow very biscit big ol boof heckin good boys and girls, shooberino boof, fluffer lotsa pats. ",Very cute and as described,5 +65,24,"Smol borking doggo with a long snoot for pats pats long water shoob what a nice floof sub woofer, woofer he made many woofs.",long water shoob adorable doggo puggorino. ,4 +66,13,"Very taste wow ur givin me a spook very taste wow borkf boofers noodle horse adorable doggo, ur givin me a spook shooberino bork most angery pupper!","Needed a little TLC, but now it's totally perfect.",3 +67,16,"Eu sit facer interesset. Deleniti offendit mei te, ut usu latine disputando. Cu nec sumo ferri. Mel id idque forensibus moderatius. Ea pro autem ceteros temporibus, probo falli an vel.",Love it,4 +68,49,"Lorem ipsum dolor sit amet, vidisse intellegebat ne mei, nibh omnium cu eos. Usu ex tale facilisi, pro ei graece mandamus. Nostro dolores elaboraret id nec, an has commodo quaeque. Eu delenit percipit ius, usu ne iuvaret vituperata constituam. Ea nec iisque eruditi atomorum. +",The tree was supposed to have arrived in a ceramic ...,3 +69,49,"Vis cu atqui propriae intellegat, putent omittam percipit te vim, vim ei error ceteros pertinax. Eam id epicurei officiis, ad mel mazim tritani. Meliore offendit in mea. Mei noluisse consectetuer ea, eam eu quas sadipscing complectitur, suscipit dissentiunt te has. Eum eu expetenda consequuntur, dissentias reprehendunt ad pri.",starsDecay,4 +70,15,"Sea autem primis voluptaria no, cu invenire pericula constituto eum, zril utinam vel ut. Ex ferri porro volumus nec. Ne has dicit partem nostrum. Nec ad voluptatum quaerendum, ad quaestio cotidieque sit. Has an elit eruditi senserit, vim meis aliquip offendit ex, ad eam commune omnesque prodesset. Eros explicari in vis, ubique maluisset philosophia ei vel, id vivendo suscipiantur eum.",Would highly recommend to a friend I hate,1 +71,52,"Eu sit facer interesset. Deleniti offendit mei te, ut usu latine disputando. Cu nec sumo ferri. Mel id idque forensibus moderatius. Ea pro autem ceteros temporibus, probo falli an vel.",Nice Plants,5 +72,8,"Debet dicant efficiantur ei sit. Sed ut duis dicit quaeque, eu deleniti molestie mei, quo harum munere dolores cu. Harum soluta cu qui. Rebum ornatus docendi ad mea, per cu primis vidisse democritum. Tale zril ex vel, prima falli fabulas ut sit, sed ei euismod repudiandae.",Please help! Tree is dying!,3 +73,36,"Lorem ipsum dolor sit amet, eum cu tation dignissim. Tota euismod contentiones est id, usu cu modus virtute, ne reque quaestio mel. Pri summo copiosae torquatos cu? Ea suas iuvaret consulatu mel, eos eu decore labitur feugiat.","The pot is nice, i like it",4 +74,3,"Prima libris intellegam eam ex, et usu quot case intellegebat. Ius ex nullam insolens, eu eos minim affert facilisis, alii dico instructior ad duo. Usu et deseruisse percipitur, ullum feugiat tractatos mea an? Consul populo mediocritatem eu sea, nec ad prima temporibus, cu veniam facilisi qui?",Wonderfull product but my pool is leaking,1 +75,5,"Autem quodsi ex pro, et graece omnium qui. Perpetua torquatos consectetuer te qui, nam ut quem fabellas? Euismod meliore molestiae his ex, te qui fabellas accusamus, nobis exerci mel et. Unum habeo verterem an ius, mea ne unum denique placerat! In ius wisi quodsi. Unum splendide et mel.",Piece of crap!,1 +76,2,"Munere vulputate voluptaria vix ex, amet liberavisse vel in. Purto indoctum mediocrem ea mea, an duo tritani omnesque, graece adipiscing sea no! His sint dicat ad, mea te nobis aperiam. Esse veri vel in, aeterno admodum per et, sed ne nobis eripuit. Feugiat fabulas te his, usu ullum dicit sententiae at, cu altera iracundia vim. Vidisse imperdiet concludaturque vel ea, sed ne labore praesent?",Mothers day gift,4 +77,14,"An altera dissentiunt vel! Viris eruditi ut est, pri harum persius no. Ex sed case omittam commune. Ei per regione constituam, an cum brute error, ea quando mollis nam. Natum elitr eos ei, mel at nonumes deleniti platonem, adhuc mandamus id mei.",Very nice!,3 +78,24,"Liber electram eam et! Usu dicam admodum te, simul adipiscing sadipscing vel te? Eum unum dolor ea! Ex his paulo ignota alienum, mei dolores perpetua adipiscing in?",very cool,5 +79,19,"No veritus volutpat pri, vim suas aliquid atomorum ea! Wisi atqui ea per, persius tacimates posidonium an has! At pri quot facilis, mei cu animal accommodare, qui an quidam petentium consequat! Omnes postea epicuri pro no, modus scripta est an. Sint deleniti eu sit, vel ex suas justo signiferumque!",Very pleased,4 +80,61,"Mollis iriure pericula ad sed, cetero luptatum ocurreret ea mei. At mentitum patrioque vis, vim saperet interesset id. Eu his erat quaeque deserunt, vix doming erroribus te, ei graeci semper his. Quo quot simul efficiantur te! Sit id regione lucilius, wisi intellegat ex mea? +",It was really sad.,2 +81,48,"Aeterno imperdiet disputationi eum id. Per atqui menandri ad. Ei per patrioque splendide prodesset, iudico persius legimus at ius. Usu ex tantas aliquid, ei vis eros labore elaboraret! Mutat tollit no sit, mei et alii unum tamquam! Eos vivendo pericula ocurreret ei, ad malis repudiare ius.","Nice plant, delivered 2 days late",2 +82,26,"Ne eam consul tritani concludaturque, id inermis detraxit nam! In novum omnesque splendide has, eam verterem adipiscing dissentiunt ea. Per mentitum complectitur ne, in pro fabulas legendos. Erant patrioque cotidieque ea quo, nusquam similique te per, case iriure maiorum ius id. Vis dico possim te?",Good product.,3 +83,4,"His ei primis omnium adolescens, in doming tritani vocibus per. Nec menandri vituperatoribus ut, eu nec error tincidunt. Te eos nostrud detraxit posidonium, mel labore nemore corpora et. Te magna senserit nam, an sonet eripuit eam. Id vix viris petentium patrioque, et sonet aliquam adipiscing mea. Pri in ponderum intellegat, nec inimicus.","The tree is luckily fine, but whoever shipped it",2 +84,36,"Lorem ipsum dolor sit amet, ex mea dolore primis. Idque delenit vivendum eam at, eam oportere definiebas eu. Cu nihil admodum eum, posse aeterno quaestio id has. ",Won't Hold Water,2 +85,16,"Mel te mentitum complectitur contentiones. Cu quem regione consetetur pro! Usu ne vivendum explicari, vix vero tollit recusabo ex. Ut purto laudem antiopam eam? Tale contentiones an eos, vel in ornatus omittam hendrerit.","Great gift, little issues",4 +86,4,"Has at probo corrumpit, vel quot repudiare ut. Pri id error audire, te sea mollis partiendo.",Great so far,3 +87,45,"Unum detracto gloriatur ius ei, sed id duis aliquip nonumes? Mei labores neglegentur ea. Partem maluisset reprehendunt eam eu, te offendit erroribus cum. Aperiri saperet accusata nam ut, ei labore molestiae sed? Dolorem imperdiet definitiones id vim.",not a very good tree for us,2 +88,1,"Minim viris denique id sit! Per cu novum salutandi cotidieque, eum cu solum vidisse, vim mazim suscipit ut. Vix at minimum evertitur constituto. Essent honestatis ei his, usu mucius doctus numquam at! Te graeci assueverit vix, ex illum debet pro, vix ei meis nusquam.",Great buy.,4 +89,54,"Ex summo vitae mei, est ea pericula petentium! Vis ea purto etiam aliquip. Eros minim ad mei, brute offendit qui et, albucius salutandi adipiscing qui in. Soleat docendi in nam, sea an utamur deseruisse. Id detraxit dissentiunt sit?",I would buy again.,3 +90,46,"Tale erat eu ius, his ad primis volumus! Audiam platonem dissentiet ex vim. Ex movet mollis fabulas vis, errem fierent intellegat per ad, doming facete ex vim? In ridens incorrupte cum, at dico ubique populo nam, in eirmod regione detraxit vel? Ludus consulatu mel ut! Ei vide habemus eam, eu mel inermis docendi euripidis, usu.",would like a replacement,2 +91,2,"Lorem ipsum dolor sit amet, brute reque propriae an quo. ", Tree Delight,5 +92,8, Ea vis movet percipit efficiendi?,Beautiful little tree,4 +93,31,"Ne mei cibo tempor maiorum, duo ut mutat facilis. ",Should have been a good product.,2 +94,38,"Has cu case fugit semper, mel te quot novum homero, pri te perfecto ocurreret! Alienum lucilius duo ut, ullum fastidii pertinax eu usu, mel ei iriure molestiae.",The one I got was very full and looks awesome. ,5 +95,34,"In nec porro vidisse utroque, deleniti euripidis mediocritatem sed at? Nec posse melius eu.",Will come like the picture,4 +96,3,"Eam esse viderer erroribus ad, iudicabit maiestatis ex vix. Ornatus praesent qui ei, cum congue luptatum ex. At fierent conceptam mel, vix case dolor at, cu justo intellegam sit. Ei pro persius nostrum albucius, te sed porro iudico.",Three Stars,3 +97,22,"Ex nec simul verterem, id discere feugiat eam. Mel vitae equidem at, at sapientem consectetuer mel, ex per errem integre numquam? ",Four Stars,4 +98,42,"Deserunt vituperata reformidans mei te, mazim intellegebat ut vim, paulo euripidis mel ex. Id mel aeterno probatus? ",Great gift for anyone!,5 +99,13," Sed euripidis adipiscing ne. Posse liber repudiare duo te, quidam fuisset constituam vis eu, libris dictas audiam ut vis. ",Hit or miss,1 +100,39,"Tota utroque constituto est et, repudiandae definitiones sea te.",Beautiful accent to your window sill.,4 +101,17,"Usu everti commodo interpretaris te. Est an tation impedit consetetur. Pro aeque platonem praesent et. Eam iracundia evertitur mnesarchum id. Te elitr inimicus philosophia duo, mea simul nusquam ei? Te tractatos tincidunt dissentiunt est!","Healthy tree, crappy pot",2 +102,7,"Ex sea omnis aliquip abhorreant, sed ei movet dicit. Nam ludus exerci inermis in, mel labore nemore scripserit no. Ius sint constituam an, cibo lucilius ea mel, falli nihil eos ea! Ex usu meis saperet postulant, vim an laudem soleat liberavisse, vix dicant invenire ea. Id soluta.",Just as promised!,4 diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 0000000000..37f225e998 --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,85 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# Note that this schema.rb definition is the authoritative source for your +# database schema. If you need to create the application database on another +# system, you should be using db:schema:load, not running all the migrations +# from scratch. The latter is a flawed and unsustainable approach (the more migrations +# you'll amass, the slower it'll run and the greater likelihood for issues). +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema.define(version: 20171024172616) do + + # These are extensions that must be enabled in order to support this database + enable_extension "plpgsql" + + create_table "merchants", force: :cascade do |t| + t.string "username" + t.string "email" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "oauth_uid", null: false + t.string "oauth_provider", null: false + end + + create_table "orderitems", force: :cascade do |t| + t.bigint "order_id" + t.bigint "product_id" + t.integer "quantity" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "status" + t.index ["order_id"], name: "index_orderitems_on_order_id" + t.index ["product_id"], name: "index_orderitems_on_product_id" + end + + create_table "orders", force: :cascade do |t| + t.string "customer_email" + t.string "address1" + t.string "address2" + t.string "city" + t.string "state" + t.string "zipcode" + t.string "cc_name" + t.string "cc_number" + t.string "cc_expiration" + t.string "cc_security" + t.string "billingzip" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "status" + t.datetime "purchase_datetime" + t.string "customer_name" + end + + create_table "products", force: :cascade do |t| + t.string "name" + t.float "price" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "quantity" + t.bigint "merchant_id" + t.string "categories", default: [], array: true + t.text "description" + t.string "photo_url" + t.string "status" + t.index ["merchant_id"], name: "index_products_on_merchant_id" + end + + create_table "reviews", force: :cascade do |t| + t.integer "rating" + t.bigint "product_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.text "review_text" + t.string "title" + t.index ["product_id"], name: "index_reviews_on_product_id" + end + + add_foreign_key "orderitems", "orders" + add_foreign_key "orderitems", "products" + add_foreign_key "products", "merchants" + add_foreign_key "reviews", "products" +end diff --git a/db/seeds.rb b/db/seeds.rb new file mode 100644 index 0000000000..0b114789d8 --- /dev/null +++ b/db/seeds.rb @@ -0,0 +1,169 @@ +require 'csv' +require 'colorize' + + +MERCHANT_FILE = Rails.root.join('db', 'merchant_seeds.csv') +puts "Loading raw merchant data from #{MERCHANT_FILE}" + +merchant_failures = [] +CSV.foreach(MERCHANT_FILE, :headers => true) do |row| + merchant = Merchant.new + merchant.id = row['id'] + merchant.username = row['username'] + merchant.email = row['email'] + merchant.oauth_uid = row['oauth_uid'] + merchant.oauth_provider = row['provider'] + puts "Created merchant: #{merchant.inspect}" + successful = merchant.save + if !successful + merchant_failures << merchant + errors = "!!!!Error!!!!" + end +end + +PRODUCT_FILE = Rails.root.join('db', 'product_seeds.csv') +puts "Loading raw product data from #{PRODUCT_FILE}" + +product_failures = [] +CSV.foreach(PRODUCT_FILE, :headers => true) do |row| + product = Product.new + product.id = row['id'] + product.name = row['name'] + product.price = row['price'] + product.categories = row['categories'] + product.merchant_id = row['merchant_id'] + product.quantity = row['quantity'] + product.status = row['status'] + product.photo_url = row['photo_url'] + product.description = row['description'] + puts "Created product: #{product.inspect}" + successful = product.save + if !successful + product_failures << product + @errors = "Error!!!!" + end +end + +ORDER_FILE = Rails.root.join('db', 'order_seeds.csv') +puts "Loading raw order data from #{ORDER_FILE}" + +order_failures = [] +CSV.foreach(ORDER_FILE, :headers => true) do |row| + order = Order.new + order.id = row['id'] + order.customer_name = row['customer_name'] + order.customer_email = row['customer_email'] + order.address1 = row['address1'] + order.address2 = row['address2'] + order.city = row['city'] + order.state = row['state'] + order.zipcode = row['zipcode'] + order.cc_name = row['cc_name'] + order.cc_number = row['cc_number'] + order.cc_expiration = row['cc_expiration'] + order.cc_security = row['cc_security'] + order.billingzip = row['billingzip'] + order.status = row['status'] + puts "Created order: #{order.inspect}" + successful = order.save + if !successful + order_failures << order + @errors = "Error!!!!" + end +end + + +ORDERITEM_FILE = Rails.root.join('db', 'orderitem_seeds.csv') +puts "Loading raw order_item data from #{ORDERITEM_FILE}" + +order_item_failures = [] +CSV.foreach(ORDERITEM_FILE, :headers => true) do |row| + order_item = Orderitem.new + order_item.id = row['id'] + order_item.order_id = row['order_id'] + order_item.product_id = row['product_id'] + order_item.quantity = row['quantity'] + puts "Created order_item: #{order_item.inspect}" + successful = order_item.save + if !successful + order_item_failures << order_item + @errors = "Error!!!!" + end +end + +REVIEW_FILE = Rails.root.join('db', 'review_seeds.csv') +puts "Loading raw review data from #{REVIEW_FILE}" + +review_failures = [] +CSV.foreach(REVIEW_FILE, :headers => true) do |row| + review = Review.new + review.id = row['id'] + review.product_id = row['product_id'] + review.review_text = row['review_text'] + review.title = row['review_title'] + review.rating = row['rating'] + puts "Created review: #{review.inspect}" + successful = review.save + if !successful + review_failures << review + errors = "!!!!Error!!!!" + end +end + + + + +# REVIEW_FILE = Rails.root.join('db', 'review_seeds.csv') +# puts "Loading raw review data from #{REVIEW_FILE}" +# +# review_failures = [] +# CSV.foreach(REVIEW_FILE, :headers => true) do |row| +# review = Review.new +# review.id = row['id'] +# review.product_id = row['product_id'] +# review.review_text = row['review_text'] +# puts "Created review: #{review.inspect}" +# successful = review.save +# if !successful +# review_failures << review +# end +# end +# +# puts "Added #{Review.count} review records" +# puts "#{review_failures.length} reviews failed to save" + +# Since we set the primary key (the ID) manually on each of the +# tables, we've got to tell postgres to reload the latest ID +# values. Otherwise when we create a new record it will try +# to start at ID 1, which will be a conflict. +puts "Manually resetting PK sequence on each table" +ActiveRecord::Base.connection.tables.each do |t| + ActiveRecord::Base.connection.reset_pk_sequence!(t) +end + + "\n\n=====================================================\n\n".colorize(:magenta ) +puts " SEEDING SUMMARIZE\n\n".colorize(:magenta ) +puts "\nAdded #{Merchant.count} merchant records" +puts "#{merchant_failures.length} merchants failed to save" +puts "\n------------------------------------" +puts "\nAdded #{Product.count} product records" +puts "#{product_failures.length} products failed to save" +puts "\n------------------------------------" +puts "\nAdded #{Order.count} order records" +puts "#{order_failures.length} orders failed to save" +puts "\n------------------------------------" +puts "\nAdded #{Orderitem.count} order_item records" +puts "#{order_item_failures.length} order_items failed to save" +puts "\n------------------------------------" +puts "\nAdded #{Review.count} reviews records" +puts "#{review_failures.length} reviews failed to save" +puts "\n------------------------------------" + +if merchant_failures.length > 0 || product_failures.length > 0 || order_failures.length > 0 || order_item_failures.length > 0 + puts "\n\n ERRORS HAPPENED!! :( !! :( !! :( !! :( !! :( !! :(".colorize(:light_red ) +else + puts "\n\n PERFECT SEEDING!!!! :) :) :) ".colorize(:green ) +end +puts "\n=====================================================".colorize(:magenta ) + +puts "done" diff --git a/lib/assets/.keep b/lib/assets/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/tasks/.keep b/lib/tasks/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/log/.keep b/log/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/package.json b/package.json new file mode 100644 index 0000000000..f874acf437 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "name": "betsy", + "private": true, + "dependencies": {} +} diff --git a/public/403.html b/public/403.html new file mode 100644 index 0000000000..06a8f0dbf2 --- /dev/null +++ b/public/403.html @@ -0,0 +1,67 @@ + + + + You cannot access this page (403) + + + + + + +
+
+

The access to this page is forbidden.

+

You do not have the necessary rights to access this page.

+
+

If you are the application owner check the logs for more information.

+
+ + diff --git a/public/404.html b/public/404.html new file mode 100644 index 0000000000..2be3af26fc --- /dev/null +++ b/public/404.html @@ -0,0 +1,67 @@ + + + + The page you were looking for doesn't exist (404) + + + + + + +
+
+

The page you were looking for doesn't exist.

+

You may have mistyped the address or the page may have moved.

+
+

If you are the application owner check the logs for more information.

+
+ + diff --git a/public/422.html b/public/422.html new file mode 100644 index 0000000000..c08eac0d1d --- /dev/null +++ b/public/422.html @@ -0,0 +1,67 @@ + + + + The change you wanted was rejected (422) + + + + + + +
+
+

The change you wanted was rejected.

+

Maybe you tried to change something you didn't have access to.

+
+

If you are the application owner check the logs for more information.

+
+ + diff --git a/public/500.html b/public/500.html new file mode 100644 index 0000000000..78a030af22 --- /dev/null +++ b/public/500.html @@ -0,0 +1,66 @@ + + + + We're sorry, but something went wrong (500) + + + + + + +
+
+

We're sorry, but something went wrong.

+
+

If you are the application owner check the logs for more information.

+
+ + diff --git a/public/apple-touch-icon-precomposed.png b/public/apple-touch-icon-precomposed.png new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000000..37b576a4a0 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1 @@ +# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file diff --git a/test/application_system_test_case.rb b/test/application_system_test_case.rb new file mode 100644 index 0000000000..d19212abd5 --- /dev/null +++ b/test/application_system_test_case.rb @@ -0,0 +1,5 @@ +require "test_helper" + +class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + driven_by :selenium, using: :chrome, screen_size: [1400, 1400] +end diff --git a/test/controllers/.keep b/test/controllers/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/controllers/merchants_controller_test.rb b/test/controllers/merchants_controller_test.rb new file mode 100644 index 0000000000..3c8e5b1210 --- /dev/null +++ b/test/controllers/merchants_controller_test.rb @@ -0,0 +1,137 @@ +require "test_helper" + +describe MerchantsController do + let(:merchant) {merchants(:sappy1)} + let(:other_merchant) {merchants(:sappy2)} + let(:merchant_order) { orders(:one) } + let(:other_merchant_order) { orders(:two) } + + describe "#fulfillment" do + it "gets the fulfillment page with a valid merchant logged in" do + login(merchant, merchant.oauth_provider.to_sym) + session[:merchant_id].to_i.must_equal merchant.id + get merchant_fulfillment_path(merchant.id) + must_respond_with :success + end + + it "redirects to the homepage when the merchant is not logged in" do + get merchant_fulfillment_path(merchant.id) + must_respond_with :redirect + flash[:result_text].must_equal "Unauthorized user" + end + + it "redirects to the homepage when an invalid merchant is logged in" do + login(other_merchant, other_merchant.oauth_provider.to_sym) + session[:merchant_id].to_i.must_equal other_merchant.id + get merchant_fulfillment_path(merchant.id) + must_respond_with :redirect + flash[:result_text].must_equal "Unauthorized user" + end + + it "redirects to the homepage when the merchant does not exist" do + get merchant_fulfillment_path(-1) + must_respond_with :redirect + flash[:result_text].must_equal "Invalid request" + end + + end + + describe "#show_order" do + + it "gets the show order page with a valid merchant logged in" do + login(merchant, merchant.oauth_provider.to_sym) + session[:merchant_id].to_i.must_equal merchant.id + get merchant_order_path(merchant.id, merchant_order.id) + must_respond_with :success + end + + it "redirects to the homepage when the merchant is not logged in" do + get merchant_order_path(merchant.id, merchant_order.id) + must_respond_with :redirect + flash[:result_text].must_equal "Unauthorized user" + end + + it "redirects to the homepage when an invalid merchant is logged in" do + login(other_merchant, other_merchant.oauth_provider.to_sym) + session[:merchant_id].to_i.must_equal other_merchant.id + get merchant_order_path(merchant.id, merchant_order.id) + must_respond_with :redirect + flash[:result_text].must_equal "Unauthorized user" + end + + it "redirects to the homepage when the merchant and order don't match" do + login(merchant, merchant.oauth_provider.to_sym) + session[:merchant_id].to_i.must_equal merchant.id + + get merchant_order_path(-1, merchant_order.id) + must_respond_with :redirect + flash[:result_text].must_equal "Invalid request" + + get merchant_order_path(merchant.id, -1) + must_respond_with :redirect + flash[:result_text].must_equal "Invalid order" + end + end + + describe "#ship_orderitems" do + before do + login(merchant, merchant.oauth_provider.to_sym) + end + + it "will respond with an error when the order doesn't have any items from that merchant" do + patch merchant_ship_path(merchant.id, other_merchant_order.id) + must_respond_with :redirect + flash[:status].must_equal :error + flash[:result_text].must_equal "Invalid order" + end + + it "will change relevant orderitems' statuses to 'shipped' with a valid order and merchant id" do + merchant_order.orderitems.each do |item| + db_item = Orderitem.find_by(order_id: item.order_id, product_id: item.product.id) + db_item.status.wont_equal "shipped" + end + patch merchant_ship_path(merchant.id, merchant_order.id) + must_respond_with :redirect + flash[:status].must_equal :success + flash[:result_text].must_equal "Order marked as shipped" + merchant_order.orderitems.each do |item| + db_item = Orderitem.find_by(order_id: item.order_id, product_id: item.product.id) + if db_item.product.merchant_id == merchant.id + db_item.status.must_equal "shipped" + end + end + end + end + + describe "logged in merchants" do + + it "merchants can access their own account page" do + login(merchant, :github) + flash[:result_text].must_equal "You successfully logged in as #{merchant.username}." + get merchant_path(merchant.id) + must_respond_with :success + end + + it "merchants can't access other merchants account page" do + login(merchant, :github) + flash[:result_text].must_equal "You successfully logged in as #{merchant.username}." + get merchant_path(other_merchant.id) + flash[:status].must_equal :error + flash[:result_text].must_equal "Unauthorized user" + end + + end + + describe "guests" do + + it "guest can't access any account/merchant page" do + get merchant_path(merchant.id) + flash[:status].must_equal :error + flash[:result_text].must_equal "Unauthorized user" + get merchant_path(other_merchant.id) + flash[:status].must_equal :error + flash[:result_text].must_equal "Unauthorized user" + end + + end +end diff --git a/test/controllers/orders_controller_test.rb b/test/controllers/orders_controller_test.rb new file mode 100644 index 0000000000..c15d285a57 --- /dev/null +++ b/test/controllers/orders_controller_test.rb @@ -0,0 +1,436 @@ +require "test_helper" + +describe OrdersController do + let(:product) {products(:tree1)} + + describe '#show_cart' do + it "gets the show_cart page with a valid session[:order_id]" do + get show_cart_path + session[:order_id].wont_equal nil + must_respond_with :success + end + + it "gets the show_cart page with an invalid session[:order_id] once the session[:order_id] has been automatically reset to a valid value" do + invalid_id = -1 + get show_cart_path + session[:order_id] = invalid_id + get show_cart_path + must_respond_with :success + session[:order_id].wont_equal invalid_id + end + end + + describe '#add_item' do + before do + get root_path #do this to get session + end + it "won't add item if the product is invalid" do + invalid_id = -1 + patch add_order_item_path(session[:order_id], invalid_id) + must_respond_with :redirect + flash[:status].must_equal :error + flash[:result_text].must_equal "That is not a valid product" + end + + it "won't add item if the product quantity is < 1" do + product.quantity = 0 + product.save.must_equal true + patch add_order_item_path(session[:order_id], product.id) + must_respond_with :redirect + flash[:status].must_equal :error + flash[:result_text].must_equal "#{product.name} is out of stock" + end + + it "will add the item if the product is valid and the product quantity is > 0" do + patch add_order_item_path(session[:order_id], product.id) + must_respond_with :redirect + flash[:status].must_equal :success + flash[:result_text].must_equal "1 #{product.name} added to your cart" + end + + it "will update the quantity of the item in cart if already added before" do + product.quantity = 2 + product.save.must_equal true + #adding first time + patch add_order_item_path(session[:order_id], product.id) + must_respond_with :redirect + flash[:status].must_equal :success + flash[:result_text].must_equal "1 #{product.name} added to your cart" + order_item = Order.find_by(id: session[:order_id]).orderitems.first + order_item.product_id.must_equal product.id + order_item.quantity.must_equal 1 + #adding second time + patch add_order_item_path(session[:order_id], product.id) + must_respond_with :redirect + flash[:status].must_equal :success + flash[:result_text].must_equal "1 #{product.name} added to your cart" + order_item = Order.find_by(id: session[:order_id]).orderitems.first + order_item.product_id.must_equal product.id + order_item.quantity.must_equal 2 + end + end + + describe '#update_quantity' do + before do + get root_path #do this to get session + Order.find_by(id: session[:order_id]).orderitems.count.must_equal 0 + #add product to cart + patch add_order_item_path(session[:order_id], product.id) + must_respond_with :redirect + flash[:result_text].must_equal "1 #{product.name} added to your cart" + order_item = Order.find_by(id: session[:order_id]).orderitems.first + order_item.product_id.must_equal product.id + order_item.quantity.must_equal 1 + end + + it "won't allow action if item is not in cart" do + invalid_id = -1 + patch update_quantity_path(session[:order_id], invalid_id) + must_respond_with :redirect + flash[:status].must_equal :error + flash[:result_text].must_equal "This item is not in your cart" + end + + it "will redirect back with a flash message if the quantity is not changed" do + patch update_quantity_path(session[:order_id], product.id), params: {quantity: 1} + must_respond_with :redirect + must_redirect_to show_cart_path + flash[:status].must_equal :success + flash[:result_text].must_equal "#{product.name} quantity is still 1" + end + + it "won't allow the quantity to be changed to < 1" do + patch update_quantity_path(session[:order_id], product.id), params: {quantity: 0} + must_respond_with :success + flash[:status].must_equal :error + flash[:result_text].must_equal "#{product.name} quantity was not changed" + end + + it "won't allow the quantity to be changed to > product quantity" do + patch update_quantity_path(session[:order_id], product.id), params: {quantity: (product.quantity + 1)} + must_respond_with :success + flash[:status].must_equal :error + flash[:result_text].must_equal "You can only order up to #{product.quantity} of #{product.name}" + end + + it "will update the quantity of an item that is in the cart" do + product.quantity.must_be :>, 1 + patch update_quantity_path(session[:order_id], product.id), params: {quantity: (product.quantity - 1)} + must_respond_with :success + flash[:status].must_equal :success + flash[:result_text].must_equal "#{product.name} quantity changed to #{product.quantity - 1}" + end + end + + describe "#remove_from_cart" do + before do + get root_path #do this to get session + Order.find_by(id: session[:order_id]).orderitems.count.must_equal 0 + end + + it "won't remove an item from an invalid cart" do + skip + end + + it "won't remove an item if the item doesn't exist in that cart" do + delete remove_from_cart_path(session[:order_id], product.id) + must_respond_with :redirect + flash[:status].must_equal :error + flash[:result_text].must_equal "This item is not in your cart" + end + + it "will remove a valid item from a valid cart" do + #add product to cart + patch add_order_item_path(session[:order_id], product.id) + must_respond_with :redirect + flash[:result_text].must_equal "1 #{product.name} added to your cart" + order_item = Order.find_by(id: session[:order_id]).orderitems.first + order_item.product_id.must_equal product.id + order_item.quantity.must_equal 1 + delete remove_from_cart_path(session[:order_id], product.id) + must_respond_with :redirect + flash[:status].must_equal :success + flash[:result_text].must_equal "#{product.name} was removed from your cart" + end + end + + describe "#checkout_form" do + + it "should get the checkout_form" do + get checkout_form_path + must_respond_with :success + end + + end + + describe "#checkout" do + + describe "Reduces the number of inventory for each product" do + before do + get root_path #do this to get session + Order.find_by(id: session[:order_id]).orderitems.count.must_equal 0 + #add product to cart + patch add_order_item_path(session[:order_id], product.id) + must_respond_with :redirect + flash[:result_text].must_equal "1 #{product.name} added to your cart" + order_item = Order.find_by(id: session[:order_id]).orderitems.first + order_item.product_id.must_equal product.id + order_item.quantity.must_equal 1 + end + + it "reduces inventory" do + patch checkout_path, params: { + customer_name: "Roger Rabbit", + customer_email: "test@test.com", + address1: "123 Test Street", + address2: "Apt 2", + city: "Seattle", + state: "WA", + zipcode: "98102", + cc_name: "Test Name", + cc_number: "4111111111111111", + cc_expiration: "10/18", + cc_security: "012", + billingzip: "98101" + } + Product.find_by(name: product.name).quantity.must_equal product.quantity - 1 + must_respond_with :redirect + end + + it "won't allow purchase of out of stock items" do + out_of_stock_product = Product.find_by(name: product.name) + out_of_stock_product.quantity = 0 + out_of_stock_product.save.must_equal true + patch checkout_path + flash[:status].must_equal :error + flash[:result_text].must_equal "#{product.name} is out of stock" + must_redirect_to show_cart_path + end + + it "won't allow purchase of more items than are available" do + # add a second item to the cart + patch add_order_item_path(session[:order_id], product.id) + flash[:result_text].must_equal "1 #{product.name} added to your cart" + order_item = Order.find_by(id: session[:order_id]).orderitems.first + order_item.product_id.must_equal product.id + order_item.quantity.must_equal 2 + # set product quantity to 1 + product.quantity = 1 + product.save.must_equal true + patch checkout_path + flash[:status].must_equal :error + flash[:result_text].must_equal "You attempted to purchase #{order_item.quantity} #{product.name}, but there are only #{product.quantity} available." + must_redirect_to show_cart_path + end + + end + + describe "changes the order status and purchase_datetime" do + before do + get root_path #do this to get session + Order.find_by(id: session[:order_id]).orderitems.count.must_equal 0 + #add product to cart + patch add_order_item_path(session[:order_id], product.id) + must_respond_with :redirect + flash[:result_text].must_equal "1 #{product.name} added to your cart" + order_item = Order.find_by(id: session[:order_id]).orderitems.first + order_item.product_id.must_equal product.id + order_item.quantity.must_equal 1 + @order = Order.find_by(id: session[:order_id]) + end + + it "changes the order status from pending to paid" do + @order.status.must_equal "pending" + patch checkout_path, params: { + customer_name: "Roger Rabbit", + customer_email: "test@test.com", + address1: "123 Test Street", + address2: "Apt 2", + city: "Seattle", + state: "WA", + zipcode: "98102", + cc_name: "Test Name", + cc_number: "4111111111111111", + cc_expiration: "10/18", + cc_security: "012", + billingzip: "98101" + } + must_respond_with :redirect + flash[:status].must_equal :success + flash[:result_text].must_equal "Your order has been placed" + Order.find_by(id: @order.id).status.must_equal "paid" + end + + it "changes the purchase_datetime to the datetime the order was placed" do + @order.purchase_datetime.must_equal nil + patch checkout_path, params: { + customer_name: "Roger Rabbit", + customer_email: "test@test.com", + address1: "123 Test Street", + address2: "Apt 2", + city: "Seattle", + state: "WA", + zipcode: "98102", + cc_name: "Test Name", + cc_number: "4111111111111111", + cc_expiration: "10/18", + cc_security: "012", + billingzip: "98101" + } + must_respond_with :redirect + flash[:status].must_equal :success + flash[:result_text].must_equal "Your order has been placed" + order_datetime = Order.find_by(id: @order.id).purchase_datetime + order_datetime.to_date.must_equal DateTime.now.to_date + order_datetime.must_be :<, DateTime.now + end + end + + describe "clears the current cart" do + before do + get root_path #do this to get session + Order.find_by(id: session[:order_id]).orderitems.count.must_equal 0 + #add product to cart + patch add_order_item_path(session[:order_id], product.id) + must_respond_with :redirect + flash[:result_text].must_equal "1 #{product.name} added to your cart" + order_item = Order.find_by(id: session[:order_id]).orderitems.first + order_item.product_id.must_equal product.id + order_item.quantity.must_equal 1 + end + + it "shows that the cart is now empty" do + patch checkout_path, params: { + customer_name: "Roger Rabbit", + customer_email: "test@test.com", + address1: "123 Test Street", + address2: "Apt 2", + city: "Seattle", + state: "WA", + zipcode: "98102", + cc_name: "Test Name", + cc_number: "4111111111111111", + cc_expiration: "10/18", + cc_security: "012", + billingzip: "98101" + } + order = Order.find_by(id: session[:order_id]) + order.wont_equal nil + order.orderitems.count.must_equal 0 + end + + it "sets a new session order id when checkout process complete" do + old_order_id = session[:order_id] + patch checkout_path, params: { + customer_name: "Roger Rabbit", + customer_email: "test@test.com", + address1: "123 Test Street", + address2: "Apt 2", + city: "Seattle", + state: "WA", + zipcode: "98102", + cc_name: "Test Name", + cc_number: "4111111111111111", + cc_expiration: "10/18", + cc_security: "012", + billingzip: "98101" + } + order = Order.find_by(id: session[:order_id]) + order.id.wont_equal old_order_id + end + + end + + describe "Properly updates order for user input" do + before do + get root_path #do this to get session + Order.find_by(id: session[:order_id]).orderitems.count.must_equal 0 + #add product to cart + patch add_order_item_path(session[:order_id], product.id) + must_respond_with :redirect + flash[:result_text].must_equal "1 #{product.name} added to your cart" + order_item = Order.find_by(id: session[:order_id]).orderitems.first + order_item.product_id.must_equal product.id + order_item.quantity.must_equal 1 + end + + it "updates fields for user input" do + order = Order.find_by(id: session[:order_id]) + patch checkout_path, params: { + customer_name: "Roger Rabbit", + customer_email: "test@test.com", + address1: "123 Test Street", + address2: "Apt 2", + city: "Seattle", + state: "WA", + zipcode: "98102", + cc_name: "Test Name", + cc_number: "4111111111111111", + cc_expiration: "10/18", + cc_security: "012", + billingzip: "98101" + } + + must_respond_with :redirect + flash[:status].must_equal :success + flash[:result_text].must_equal "Your order has been placed" + paid_order = Order.find_by(id: order.id) + paid_order.customer_name.must_equal "Roger Rabbit" + paid_order.customer_email.must_equal "test@test.com" + paid_order.address1.must_equal "123 Test Street" + paid_order.address2.must_equal "Apt 2" + paid_order.city.must_equal "Seattle" + paid_order.state.must_equal "WA" + paid_order.zipcode.must_equal "98102" + paid_order.cc_name.must_equal "Test Name" + paid_order.cc_number.must_equal "4111111111111111" + paid_order.cc_expiration.must_equal "10/18" + paid_order.cc_security.must_equal "012" + paid_order.billingzip.must_equal "98101" + end + + it "validates user input" do + skip + end + end + + end #checkout + + describe "#confirmation" do + before do + get root_path #do this to get session + @order = Order.find_by(id: session[:order_id]) + patch add_order_item_path(session[:order_id], product.id) + flash[:result_text].must_equal "1 #{product.name} added to your cart" + patch checkout_path, params: { + customer_name: "Roger Rabbit", + customer_email: "test@test.com", + address1: "123 Test Street", + address2: "Apt 2", + city: "Seattle", + state: "WA", + zipcode: "98102", + cc_name: "Test Name", + cc_number: "4111111111111111", + cc_expiration: "10/18", + cc_security: "012", + billingzip: "98101" + } + must_respond_with :redirect + flash[:result_text].must_equal "Your order has been placed" + end + + it "must get the confirmation page for a valid order" do + get order_confirmation_path(@order.id) + must_respond_with :success + end + + it "must not get the confirmation page for an invalid order" do + get order_confirmation_path(-1) + must_respond_with :redirect + flash[:result_text].must_equal "Invalid order" + end + end + + +end #OrdersController diff --git a/test/controllers/products_controller_test.rb b/test/controllers/products_controller_test.rb new file mode 100644 index 0000000000..136d5ce2d6 --- /dev/null +++ b/test/controllers/products_controller_test.rb @@ -0,0 +1,351 @@ +require "test_helper" + +describe ProductsController do + let(:one) {products(:tree1)} + let(:two) {products(:tree2)} + let(:three) {products(:tree3)} + + describe "index" do + it "must get the index view" do + get products_path + must_respond_with :success + end + + it "must respond with success with valid filter parameters" do + #valid category + get products_path, params: {categories: one.categories} + must_respond_with :success + #valid merchant + get products_path, params: {merchant: one.merchant.id} + must_respond_with :success + #valid category and merchant + get products_path, params: {categories: one.categories, merchant: one.merchant.id} + must_respond_with :success + end + + it "must respond with a 404 (400?) if any filter parameters are invalid" do + skip + end + end + + describe "show" do + it "must get the show view" do + get product_path(one.id) + must_respond_with :success + end + + it "must render a 404 not found for nonvalid product id" do + get product_path(-1) + must_respond_with :not_found + end + + it "must render a 404 not found for nonvalid product id" do + get product_path(-1) + must_respond_with :not_found + end + + it "must render a 404 not found for retired products for guest users" do + get product_path(three.id) + must_respond_with :not_found + end + + it "must render a 404 not found to merchants trying to see a retired product they do not own " do + @merchant = merchants(:sappy2) + login(@merchant, :github) + flash[:result_text].must_equal "You successfully logged in as #{@merchant.username}." + get product_path(three.id) + must_respond_with :not_found + end + + it "must get show for retired products if their owner is logged in" do + @merchant = merchants(:sappy1) + login(@merchant, :github) + flash[:result_text].must_equal "You successfully logged in as #{@merchant.username}." + get product_path(three.id) + must_respond_with :success + end + end + + + describe "new product" do + it "should get new if a merchant is signed in" do + @merchant = merchants(:sappy1) + login(@merchant, :github) + get new_product_path + must_respond_with :success + end + + it "must redirect to root path if a merchant is not signed in" do + # With no mercant signed in + post logout_path + get new_product_path + session[:merchant_id].must_equal nil + must_respond_with :redirect + flash[:status].must_equal :error + flash[:result_text].must_equal "You need to be logged in to create a product!" + must_redirect_to root_path + + end + end + + describe "create product with signed in merchant" do + before do + @merchant = merchants(:sappy1) + login(@merchant, :github) + end + + it "should be able to successfully create a new product" do + proc { + post products_path, params: { name: "newbie", price: 2, category: "new category", quantity: 1, merchant_id: @merchant.id, status: "active" } + }.must_change 'Product.count', 1 + must_respond_with :redirect + end + + it "should not create the product if it is missing a name" do + proc { + post products_path, params: { name: "", price: 2, category: "new category", quantity: 1, merchant_id: merchants(:sappy1).id } + }.must_change 'Product.count', 0 + + must_respond_with :bad_request + flash[:status].must_equal :error + end + + it "should not create the product if it is missing a price" do + proc { + post products_path, params: { name: "Name", category: "new category", quantity: 1, merchant_id: merchants(:sappy1).id } + }.must_change 'Product.count', 0 + + must_respond_with :bad_request + flash[:status].must_equal :error + end + + it "should record all the values of the product" do + proc { + post products_path, params: { name: "Name", price: 50, category: "new category", description: "This is a new product", photo_url: "www.google.com", quantity: 1, merchant_id: merchants(:sappy1).id, status: "retired" } + }.must_change 'Product.count', 1 + + product = Product.find_by(name: "Name") + product.price.must_equal 50 + product.categories.must_include "new category" + product.categories.length.must_equal 1 + product.description.must_equal "This is a new product" + product.merchant.must_equal merchants(:sappy1) + product.quantity.must_equal 1 + product.photo_url.must_equal "www.google.com" + end + end + + describe "create product without signed in merchant" do + it "should not create a product if a merchant is not signed in" do + + proc { + post products_path, params: { name: "Name", price: 50, category: "new category", description: "This is a new product", photo_url: "www.google.com", quantity: 1, merchant_id: merchants(:sappy1).id } + }.must_change 'Product.count', 0 + + must_respond_with :redirect + flash[:status].must_equal :error + end + end + + describe "edit with a merchant signed in" do + let(:one) {products(:tree1)} + let(:two) {products(:tree2)} + + it "must return status :not_found if there is no product" do + @merchant = merchants(:sappy1) + login(@merchant, :github) + + get product_path(-1) + must_respond_with :not_found + end + + it "must show edit view if the merchant is logged in and there is a product" do + @merchant = merchants(:sappy1) + login(@merchant, :github) + + get edit_product_path(one.id) + must_respond_with :success + end + end + + describe "edit without the owning merchant signed in" do + it "must redirect to root path if the merchant of the product is not signed in" do + one = products(:tree1) + + # With no mercant signed in + get edit_product_path(one.id) + must_respond_with :redirect + must_redirect_to root_path + + #With an invalid merchant signed in + invalid_merchant = merchants(:sappy2) + login(invalid_merchant, :github) + + get edit_product_path(one.id) + flash[:status].must_equal :error + flash[:result_text] = "Unauthorized user" + must_respond_with :redirect + must_redirect_to root_path + end + end + + describe "update action with a valid merchant signed in" do + let(:merchant1) {merchants(:sappy1)} + let(:one) {products(:tree1)} + + before do + login(merchant1, :github) + end + + it "successfully updates the categories of the product when selected" do + session[:merchant_id].to_i.must_equal merchant1.id + + #format passed through params if chosen from a list + before = one.categories.count + + patch product_path(one.id), params:{ categories: ["outdoor"]} + + test_product = Product.find_by(id: one.id) + + test_product.categories.length.must_equal (before + 1) + test_product.categories.must_include "outdoor" + end + + it "sucessfully updates the categories of a product when written" do + #format passed through params if typed in + before = one.categories.count + patch product_path(one.id), params:{ category: "Birthday"} + + test_product = Product.find_by(id: one.id) + + test_product.categories.count.must_equal (before + 1) + test_product.categories.must_include "Birthday" + end + + it "successfully updates the categories when passed in both written and selected" do + before = one.categories.count + patch product_path(one.id), params:{ category: "Birthday", categories: ["outdoor"] } + + test_product = Product.find_by(id: one.id) + + test_product.categories.count.must_equal (before + 2) + end + + it "can update when multiple categories are selected" do + before = one.categories.count + patch product_path(one.id), params:{categories: ["outdoor", "spooky", "fiesta"] } + + test_product = Product.find_by(id: one.id) + + test_product.categories.count.must_equal (before + 3) + end + + it "updates all categories available if the merchant writes in" do + Product.categories.include?("Birthday").must_equal false + patch product_path(one.id), params:{ category: "Birthday"} + + Product.categories.include?("Birthday").must_equal true + end + + it "successfully updates the title, price, and quantity of the product" do + patch product_path(one.id), params:{ name: "New Tree", price: 5.0, quantity: 15 } + + test_product = Product.find_by(id: one.id) + + test_product.name.must_equal "New Tree" + test_product.price.must_equal 5.0 + test_product.quantity.must_equal 15 + + flash[:status].must_equal :success + must_redirect_to product_path(one.id) + end + + it "must return succes and redirect to root_path if saved" do + login(merchant1, :github) + + session[:merchant_id].to_i.must_equal merchant1.id + + patch product_path(one.id), params:{ categories: ["outdoor"]} + + flash[:status].must_equal :success + must_redirect_to product_path(one.id) + end + + describe "will not update" do + let(:merchant2) {merchants(:sappy2)} + let(:one) {products(:tree1)} + + it "will render_404 if the product is invalid" do + patch product_path(-1), params: { categories: ["Birthday"] } + get product_path(-1) + must_respond_with :not_found + end + + it "will redirect if the merchant of the product is not signed in" do + #Invalid merchant is signed in + login(merchant2, :github) + + patch product_path(one.id), params:{categories: ["Birthday"]} + flash[:status].must_equal :error + flash[:result_text].must_equal "Unauthorized user" + must_redirect_to root_path + + #no merchant signed in + logout_path + patch product_path(one.id), params:{categories: ["Birthday"]} + flash[:status].must_equal :error + flash[:result_text].must_equal "Unauthorized user" + must_redirect_to root_path + end + + it "will not update an invalid attribute" do + login(merchants(:sappy1), :github) + + patch product_path(one.id), params:{price: "hello"} + + test_product = Product.find_by(id: one.id) + + test_product.name.must_equal "Tree1" + flash[:status].must_equal :error + must_respond_with :bad_request + end + end + end + describe "retire action" do + it "retire can't be used by a guest" do + get retire_path(one.id) + flash[:status].must_equal :error + flash[:result_text].must_equal "Unauthorized user" + must_redirect_to root_path + end + it "retire can't be used by a merchant other than the product's owner" do + merchant = merchants(:sappy1) + login(merchant, :github) + flash[:result_text].must_equal "You successfully logged in as #{merchant.username}." + get retire_path(two.id) + flash[:status].must_equal :error + flash[:result_text].must_equal "Unauthorized user" + must_redirect_to root_path + end + it "retire can be used by the merchant who own the product" do + merchant = merchants(:sappy1) + login(merchant, :github) + flash[:result_text].must_equal "You successfully logged in as #{merchant.username}." + get retire_path(one.id) + flash[:status].must_equal :success + must_redirect_to root_path + end + it "changes the status of a product" do + merchant = merchants(:sappy1) + login(merchant, :github) + one.status = "active" + one.save.must_equal true + get retire_path(one.id) + flash[:status].must_equal :success + Product.find_by(id: one.id).status.must_equal "retired" + get retire_path(one.id) + flash[:status].must_equal :success + Product.find_by(id: one.id).status.must_equal "active" + end + end +end diff --git a/test/controllers/reviews_controller_test.rb b/test/controllers/reviews_controller_test.rb new file mode 100644 index 0000000000..411633fb06 --- /dev/null +++ b/test/controllers/reviews_controller_test.rb @@ -0,0 +1,78 @@ +require "test_helper" + +describe ReviewsController do + let(:product) {products(:tree1)} + let(:merchant) {merchants(:sappy1)} + + it "should get new if no one is signed in" do + get new_review_path(products(:tree1).id) + must_respond_with :success + end + + it "should redirect to the product page if the merchant of the product is signed in" do + login(merchant, :github) + flash[:status] = :error + must_respond_with :redirect + # must_redirect_to product_path(product.id) + end + + it "should be able to successfully create a review" do + proc { + post reviews_path, params: { review: { title: "Review Title", review_text: "Super duper review", rating: 4, product_id: products(:tree1).id } } + }.must_change 'Review.count', 1 + + must_respond_with :redirect + end + + it "a merchant can't access the review form for their own product" do + login(merchant, :github) + flash[:status] = :success + get new_review_path(product.id) + flash[:status] = :error + flash[:result_text] = "Merchant can't leave reviews of their own products, sorry! Wink-wink." + must_redirect_to product_path(product.id) + end + + it "should rerender the form and not update the review table if it can't create the review" do + proc { + post reviews_path, params: { review: { title: "", review_text: "Super duper review", rating: 4, product_id: products(:tree1).id } } + }.must_change 'Review.count', 0 + + must_respond_with :redirect + end + + it "should reroute a merchant to the product page and a flash message should notify them that they can't leave a review " do + @merchant = merchants(:sappy1) + login(@merchant, :github) + proc { + post reviews_path, params: { review: { title: "Super title", review_text: "Super duper review", rating: 4, product_id: products(:tree1).id } } + }.must_change 'Review.count', 0 + must_respond_with :forbidden + end + + + + # it "should get show" do + # get reviews_show_url + # value(response).must_be :success? + # end + # + # + # it "should get edit" do + # get reviews_edit_url + # value(response).must_be :success? + # end + # + # it "should get update" do + # get reviews_update_url + # value(response).must_be :success? + # end + # + # it "should get index" do + # get reviews_index_url + # value(response).must_be :success? + # end + # + + +end diff --git a/test/controllers/sessions_controller_test.rb b/test/controllers/sessions_controller_test.rb new file mode 100644 index 0000000000..858050d15f --- /dev/null +++ b/test/controllers/sessions_controller_test.rb @@ -0,0 +1,51 @@ +require "test_helper" + +describe SessionsController do + + it "logs in an existing merchant and redirect to root_path" do + @merchant = merchants(:sappy1) + start_count = Merchant.count + login(@merchant, :github) + Merchant.count.must_equal start_count + must_redirect_to merchant_path(@merchant.id) + session[:merchant_id].must_equal @merchant.id + end + + it "must have a username" do + @merchant = merchants(:sappy1) + login(@merchant, :github) + @merchant.username.must_equal "sappy1" + end + + it "logs in a new user" do + @merchant = Merchant.new(oauth_provider: "github", oauth_uid: 9999, username: "toolazytomakeone", email: "toolazytomakeone@gmail.com") + + proc {login(@merchant, :github)}.must_change 'Merchant.count', +1 + + merchant = Merchant.find_by(username: "toolazytomakeone") + + must_redirect_to merchant_path(merchant.id) + session[:merchant_id].must_equal Merchant.find_by(username: "toolazytomakeone").id + end + + it "clears the session and redirects back to the root path when a merchant logs out" do + @merchant = merchants(:sappy1) + login(@merchant, :github) + post logout_path + session[:merchant_id].must_equal nil + must_redirect_to root_path + end + + it "notifies ther merchant after it logs out" do + @merchant = merchants(:sappy1) + login(@merchant, :github) + post logout_path + flash[:status].must_equal :success + flash[:result_text].must_equal "You successfully logged out." + end + + + + + +end diff --git a/test/fixtures/.keep b/test/fixtures/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/fixtures/files/.keep b/test/fixtures/files/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/fixtures/merchants.yml b/test/fixtures/merchants.yml new file mode 100644 index 0000000000..de0608adc5 --- /dev/null +++ b/test/fixtures/merchants.yml @@ -0,0 +1,13 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +sappy1: + oauth_provider: github + oauth_uid: 1111 + username: sappy1 + email: sappy1@gmail.com + +sappy2: + oauth_provider: github + oauth_uid: 2222 + username: sappy2 + email: sappy2@gmail.com diff --git a/test/fixtures/orderitems.yml b/test/fixtures/orderitems.yml new file mode 100644 index 0000000000..c6551aab0b --- /dev/null +++ b/test/fixtures/orderitems.yml @@ -0,0 +1,43 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +# This model initially had no columns defined. If you add columns to the +# model remove the "{}" from the fixture names and add the columns immediately +# below each fixture, per the syntax in the comments below +# +one: + quantity: 1 + order: one + product: tree1 + +two: + quantity: 1 + order: two + product: tree2 + +three: + quantity: 1 + order: three + product: tree1 + +four: + quantity: 1 + order: four + product: tree1 + +5: + quantity: 2 + order: one + product: tree2 + +6: + quantity: 1 + order: five + product: tree3 + +7: + quantity: 1 + order: five + product: tree1 + +# column: value +# diff --git a/test/fixtures/orders.yml b/test/fixtures/orders.yml new file mode 100644 index 0000000000..34d041b40b --- /dev/null +++ b/test/fixtures/orders.yml @@ -0,0 +1,84 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +pending_customer: + customer_email: nil + address1: nil + address2: nil + city: nil + state: nil + zipcode: nil + cc_name: nil + cc_number: nil + cc_expiration: nil + cc_security: nil + billingzip: nil + +one: + customer_email: MyString + address1: MyString + address2: MyString + city: MyString + state: MyString + zipcode: 1 + cc_name: MyString + cc_number: 1 + cc_expiration: MyString + cc_security: 1 + billingzip: 1 + status: paid + +two: + customer_email: MyString + address1: MyString + address2: MyString + city: MyString + state: MyString + zipcode: 1 + cc_name: MyString + cc_number: 1 + cc_expiration: MyString + cc_security: 1 + billingzip: 1 + status: complete + +three: + customer_email: MyString + address1: MyString + address2: MyString + city: MyString + state: MyString + zipcode: 1 + cc_name: MyString + cc_number: 1 + cc_expiration: MyString + cc_security: 1 + billingzip: 1 + status: pending + +four: + customer_email: MyString + address1: MyString + address2: MyString + city: MyString + state: MyString + zipcode: 1 + cc_name: MyString + cc_number: 1 + cc_expiration: MyString + cc_security: 1 + billingzip: 1 + status: cancelled + +five: + customer_email: MyString + address1: MyString + address2: MyString + city: MyString + state: MyString + zipcode: 1 + cc_name: MyString + cc_number: 1 + cc_expiration: MyString + cc_security: 1 + billingzip: 1 + status: paid diff --git a/test/fixtures/products.yml b/test/fixtures/products.yml new file mode 100644 index 0000000000..95178feb49 --- /dev/null +++ b/test/fixtures/products.yml @@ -0,0 +1,27 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +tree1: + name: Tree1 + price: 10 + merchant: sappy1 + categories: [indoor] + quantity: 12 + status: "active" + + +tree2: + name: Tree2 + price: 20 + merchant: sappy2 + categories: [outdoor] + quantity: 10 + status: "active" + description: This is a tree + +tree3: + name: Tree3 + price: 30 + merchant: sappy1 + categories: [christmas] + quantity: 12 + status: "retired" diff --git a/test/fixtures/reviews.yml b/test/fixtures/reviews.yml new file mode 100644 index 0000000000..32ef94eb2e --- /dev/null +++ b/test/fixtures/reviews.yml @@ -0,0 +1,14 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +review1: + rating: 2 + title: Super review + review_text: Ohlala + product: tree1 + + +review2: + rating: 5 + title: Mediocre review + review_text: Boooooh + product: tree2 diff --git a/test/helpers/.keep b/test/helpers/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/helpers/products_helper_test.rb b/test/helpers/products_helper_test.rb new file mode 100644 index 0000000000..62183b0fe3 --- /dev/null +++ b/test/helpers/products_helper_test.rb @@ -0,0 +1,16 @@ +require "test_helper" + +describe ProductsHelper do + let(:one) {products(:tree1)} + let(:two) {products(:tree2)} + + describe "#product_description" do + it "returns properly if the description exists" do + product_description(one).must_equal "No description." + end + + it "returns properly if the description is nil" do + product_description(two).must_equal two.description + end + end +end diff --git a/test/integration/.keep b/test/integration/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/mailers/.keep b/test/mailers/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/models/.keep b/test/models/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/models/merchant_test.rb b/test/models/merchant_test.rb new file mode 100644 index 0000000000..7f5d6a7c44 --- /dev/null +++ b/test/models/merchant_test.rb @@ -0,0 +1,117 @@ +require "test_helper" + #needs to write in two tests for scopes on the model + +describe Merchant do + let(:merchant) { Merchant.new } + let(:sappy1) { merchants(:sappy1) } + let(:product) { products(:tree1) } + + it "must have a username" do + sappy1.valid?.must_equal true + sappy1.username = nil + sappy1.valid?.must_equal false + sappy1.save + sappy1.errors.keys.must_include :username + end + + it "must have a unique username" do + sappy1.username = "sappy2" + sappy1.valid?.must_equal false + end + + it "must have an email address" do + sappy1.valid?.must_equal true + sappy1.email = nil + sappy1.valid?.must_equal false + sappy1.save + sappy1.errors.keys.must_include :email + end + + it "must have a unique email" do + sappy1.email = "sappy2@gmail.com" + sappy1.valid?.must_equal false + end + + it "must return the products of the merchant" do + sappy1.products.must_be_kind_of Enumerable + sappy1.products.each do |product| + product.must_be_kind_of Product + end + end + + describe "#join_orderitems(status)" do + it "won't return any pending orders, even if pending is the specified status filter" do + # verify that sappy1 has a pending order + pending_orders = Order.where(status: "pending") + merchant_pending_orders = 0 + pending_orders.each do |order| + order.orderitems.each do |item| + merchant_pending_orders += 1 if item.product.merchant_id == sappy1.id + end + end + # verify that the pending order won't show up in the collection returned by join_orderitems(status) + merchant_pending_orders.must_be :>, 0 + all_order_items = sappy1.join_orderitems("") + all_order_items.length.must_equal 4 + all_order_items.each do |item| + item.order.status.wont_equal "pending" + end + sappy1.join_orderitems("pending").length.must_equal 0 + end + + it "returns all orders when a status is an empty string or nil" do + sappy1_orderitems = sappy1.join_orderitems("") + sappy1_orderitems.length.must_equal 4 + sappy1.join_orderitems(nil).must_equal sappy1_orderitems + end + + it "returns filtered orders by status when status is an appropriate value" do + ["paid", "cancelled"].each do |status| + sappy1_orderitems = sappy1.join_orderitems(status) + sappy1_orderitems.each do |item| + item.order.status.must_equal status + end + end + end + end + + describe "active merchants" do + + + it "returns a uniq array of active merchants" do + merchants = Merchant.active_merchants + merchants.must_be_kind_of Array + merchants.each do |m| + m.must_be_instance_of Merchant + end + + merchants.uniq.must_equal merchants + end + + it "will include merchants that have both retired and active products" do + Merchant.active_merchants.must_include merchants(:sappy1) + end + + it "will return an empty arrray if no merchants have any products" do + Orderitem.all.destroy_all + Review.all.destroy_all + Product.all.destroy_all + + Merchant.active_merchants.must_be_kind_of Array + Merchant.active_merchants.length.must_equal 0 + end + + it "will return an empty array if no merchants have any active products" do + Product.all.each do |p| + p.status = "retired" + p.save + end + + Merchant.active_merchants.must_be_instance_of Array + Merchant.active_merchants.length.must_equal 0 + end + + end + + +end #describe merchant diff --git a/test/models/order_test.rb b/test/models/order_test.rb new file mode 100644 index 0000000000..68a31ce6b2 --- /dev/null +++ b/test/models/order_test.rb @@ -0,0 +1,236 @@ +require "test_helper" + +describe Order do + let(:one) { orders(:one) } + let(:two) { orders(:two) } + + describe "validations" do + let(:one) { orders(:one) } + it "must have one or more order items" do + one.orderitems.each do |o| + o.must_be_kind_of Orderitem + end + end + + it "can access products in the order" do + one.products.each do |o| + o.must_be_kind_of Product + end + end + end + + describe "checkout validations" do + it "can have nil values if the status of the order is pending" do + order = Order.new(status: "pending") + order.valid?.must_equal true + end + + it "must have customer_name" do + order = Order.new(status: "paid" ) + order.valid?.must_equal false + + order.errors.messages.must_include :customer_name + order.errors.messages[:customer_name].must_include "name cannot be blank" + end + + it "must have customer_email" do + order = Order.new(status: "paid" ) + order.valid?.must_equal false + order.errors.messages.must_include :customer_email + order.errors.messages[:customer_email].must_include "customer email cannot be blank" + end + + it "must have a valid email address" do + order = Order.new(status: "paid", customer_email: "yahoo") + order.valid?.must_equal false + order.errors.messages.must_include :customer_email + order.errors.messages[:customer_email].must_include "invalid email format" + end + + it "will accept a valid email address" do + order = Order.new(status: "paid", customer_email: "cool@yahoo.com") + + order.valid?.must_equal false + order.errors.messages.wont_include :customer_email + end + + it "must have an address if the status of the order is not pending" do + order = Order.new(status: "paid" ) + order.valid?.must_equal false + + order.errors.messages.must_include :address1 + order.errors.messages[:address1].must_include "address cannot be blank" + end + + it "must have a city" do + order = Order.new(status: "paid" ) + order.valid?.must_equal false + + order.errors.messages.must_include :city + order.errors.messages[:city].must_include "city cannot be blank" + end + + it "must have a valid city name" do + order = Order.new(status: "paid", city: "Too---long") + order.valid?.must_equal false + order.errors.messages.must_include :city + order.errors.messages[:city].must_include "invalid city name" + end + + it "must have have a state" do + order = Order.new(status: "paid" ) + order.valid?.must_equal false + + order.errors.messages.must_include :state + order.errors.messages[:state].must_include "state cannot be blank" + end + + it "must have a valid state code" do + order = Order.new(status: "paid", state: "PK") + order.valid?.must_equal false + order.errors.messages.must_include :state + order.errors.messages[:state].must_include "invalid state abbreviation" + end + + it "must have a zipcode" do + order = Order.new(status: "paid" ) + order.valid?.must_equal false + order.errors.messages.must_include :zipcode + order.errors.messages[:zipcode].must_include "zipcode cannot be blank" + end + + it "must have a valid US zipcode" do + order = Order.new(status: "paid", zipcode: "ABCDE") + order.valid?.must_equal false + order.errors.messages.must_include :zipcode + order.errors.messages[:zipcode].must_include "invalid zipcode" + end + + it "must have a cc_name" do + order = Order.new(status: "paid" ) + order.valid?.must_equal false + + order.errors.messages.must_include :cc_name + order.errors.messages[:cc_name].must_include "name for credit card cannot be blank" + end + + it "must have a cc_number" do + order = Order.new(status: "paid" ) + order.valid?.must_equal false + + order.errors.messages.must_include :cc_number + order.errors.messages[:cc_number].must_include "credit card number cannot be blank" + end + + it "must have a valid cc_number" do + order = Order.new(status: "paid", cc_number: 123456789) + order.valid?.must_equal false + order.errors.messages.must_include :cc_number + order.errors.messages[:cc_number].must_include "invalid credit card number" + end + + it "must have a cc_expiration" do + order = Order.new(status: "paid" ) + order.valid?.must_equal false + + order.errors.messages.must_include :cc_expiration + order.errors.messages[:cc_expiration].must_include "expiration date can\'t be blank" + end + + it "must have a valid expiration date format" do + + order = Order.new(status: "paid", cc_expiration: "13/2000" ) + order.valid?.must_equal false + + order.errors.messages.must_include :cc_expiration + order.errors.messages[:cc_expiration].must_include "invalid expiration date format" + end + + it "can't have an expiration date in the past" do + order = Order.new(status: "paid", cc_expiration: "11/16" ) + order.valid?.must_equal false + + order.errors.messages.must_include :cc_expiration + + order.errors.messages[:cc_expiration].must_include "expiration date can\'t be in the past" + end + + it "can have an expiration date in the future" do + order = Order.new(status: "paid", cc_expiration: "12/17" ) + order.valid?.must_equal false + order.errors.messages.wont_include :cc_expiration + order.errors.messages[:cc_expiration].wont_include "expiration date can\'t be in the past" + end + + it "must have a cc_security" do + order = Order.new(status: "paid" ) + order.valid?.must_equal false + + order.errors.messages.must_include :cc_security + order.errors.messages[:cc_security].must_include "CVV cannot be blank" + end + + it "must have a valid (format) CVV security" do + order = Order.new(status: "paid", cc_security: "ABC") + order.valid?.must_equal false + order.errors.messages.must_include :cc_security + order.errors.messages[:cc_security].must_include "invalid CVV" + end + + it "must have a billingzip" do + order = Order.new(status: "paid" ) + order.valid?.must_equal false + + order.errors.messages.must_include :billingzip + order.errors.messages[:billingzip].must_include "billing zipcode cannot be blank" + end + + it "must have a valid US billing zipcode" do + order = Order.new(status: "paid", billingzip: "ABCDE") + order.valid?.must_equal false + order.errors.messages.must_include :billingzip + order.errors.messages[:billingzip].must_include "invalid billing zipcode" + end + end + + describe "#status_check" do + it "will change the status of an order from 'paid' to 'complete' if all items are shipped" do + one.status.must_equal "paid" + one.orderitems.each do |item| + item.status.wont_equal "shipped" + item.status = "shipped" + item.save.must_equal true + one.status.must_equal "paid" + end + one.status_check + one.status.must_equal "complete" + end + + it "won't change the status of an order if all items still need to be shipped" do + one.status.must_equal "paid" + one.orderitems.each do |item| + item.status.wont_equal "shipped" + end + one.status_check + one.status.must_equal "paid" + end + + it "won't change the status of an order is some items still need to be shipped" do + one.status.must_equal "paid" + one.orderitems.first do |item| + item.status.wont_equal "shipped" + item.status = "shipped" + item.save.must_equal true + one.status.must_equal "paid" + end + one.status_check + one.status.must_equal "paid" + end + + it "won't change the status of an order that is already complete" do + two.status.must_equal "complete" + two.status_check + two.status.must_equal "complete" + end + end +end diff --git a/test/models/orderitem_test.rb b/test/models/orderitem_test.rb new file mode 100644 index 0000000000..9fc7ad8da0 --- /dev/null +++ b/test/models/orderitem_test.rb @@ -0,0 +1,55 @@ +require "test_helper" + +describe Orderitem do + let(:one) { orderitems(:one) } + + it "has an order" do + one.order.must_be_kind_of Order + end + + it "has a product" do + one.product.must_be_kind_of Product + end + + it "quantity must be present" do + one.valid?.must_equal true + one.quantity = nil + one.valid?.must_equal false + end + + it "quantity must be an integer" do + one.valid?.must_equal true + one.quantity = 4.5 + one.valid?.must_equal false + one.quantity = "treehouse" + one.valid?.must_equal false + end + + it "must be quantity greater than 0" do + one.valid?.must_equal true + one.quantity = 1 + one.valid?.must_equal true + one.quantity = 0 + one.valid?.must_equal false + one.quantity = -1 + one.valid?.must_equal false + end + + it "must have a product" do + one.valid?.must_equal true + one.product = nil + one.valid?.must_equal false + end + + it "must have an order" do + one.valid?.must_equal true + one.order = nil + one.valid?.must_equal false + end + + it "calculates subtotal for order items" do + one.subtotal.must_equal 10.0 + end + + +end diff --git a/test/models/product_test.rb b/test/models/product_test.rb new file mode 100644 index 0000000000..de119332f4 --- /dev/null +++ b/test/models/product_test.rb @@ -0,0 +1,205 @@ +require "test_helper" + #needs to write in two tests for scopes on the model + +describe Product do + let(:product) { Product.new } + let(:tree1) {products(:tree1)} + let(:tree2) {products(:tree2)} + let(:tree3) {products(:tree3)} + + before do + tree1.valid?.must_equal true + end + + describe "validations" do + it "must have a name" do + tree1.name = nil + tree1.valid?.must_equal false + tree1.save + tree1.errors.keys.must_include :name + end + + it "must have a unique name" do + tree1.name = "Tree2" + tree1.valid?.must_equal false + end + + it "must have a price" do + tree1.price = nil + tree1.valid?.must_equal false + end + + it "price must be a number" do + tree1.price = nil + tree1.valid?.must_equal false + tree1.price = "haha" + tree1.valid?.must_equal false + end + + it "price must be greater than 0" do + tree1.price = 1 + tree1.valid?.must_equal true + tree1.price = 0 + tree1.valid?.must_equal false + tree1.price = -1 + tree1.valid?.must_equal false + end + + it "must belong to a merchant" do + tree1.merchant = nil + tree1.valid?.must_equal false + end + + it "merchant must be a kind of merchant object" do + tree1.merchant.must_be_kind_of Merchant + end + + it "must have an integer quantity" do + tree1.quantity = 2.3 + tree1.valid?.must_equal false + tree1.quantity = "two" + tree1.valid?.must_equal false + end + + it "must have a non-negative quantity" do + tree1.quantity = 0 + tree1.valid?.must_equal true + tree1.quantity = -1 + tree1.valid?.must_equal false + end + + it "must have a status" do + tree1.status = nil + tree1.valid?.must_equal false + end + + it "must have a status of 'active' or 'retired' " do + tree1.status = "retired" + tree1.valid?.must_equal true + tree1.status = "inactive" + tree1.valid?.must_equal false + tree1.status = true + tree1.valid?.must_equal false + end + end + + describe "Product.get_products method" do + let(:merchant) { merchants(:sappy1) } + let(:categories) { products(:tree1).categories } + let(:product1) { products(:tree1)} + + it "returns all products when the user does not filter category or merchant" do + products = Product.get_products() + products.must_equal Product.all + end + + it "filters products by category" do + products = Product.get_products(a_category: categories) + + products.must_equal Product.where("categories @> ?", "{#{categories}}") + end + + it "filters products by merchant" do + products = Product.get_products(a_merchant: merchant.id) + products.must_equal Product.where(merchant_id: merchant.id) + end + + it "filters products by category and merchant" do + products = Product.get_products(a_category: categories, a_merchant: merchant.id) + + # products = Product.where("categories @> {#{a_category}} AND merchant_id = #{a_merchant}") + + products.must_equal [product1] + end + it "does returns all products when the user submits invalid filters" do + skip + #TODO: Write the method to prevent users from inputting invalid filter params + products = Product.get_products(a_category: "fake category") + products.must_equal Product.all + end + end + + it "can retrieve reviews" do + tree1.reviews.each do |r| + r.must_be_kind_of Review + end + end + + describe "self.categories" do + let(:tree1) {products(:tree1)} + let(:tree2) {products(:tree2)} + + it "returns an array the categories that belong to active products" do + categories = Product.categories + categories.must_be_kind_of Array + Product.categories.must_include "indoor" + Product.categories.must_include "outdoor" + Product.categories.wont_include "christmas" + end + + it "returns an empty array if there are no categories attached to any products" do + tree1.categories.clear + tree1.save + tree2.categories.clear + tree2.save + tree3.categories.clear + tree3.save + + categories = Product.categories + categories.must_be_kind_of Array + categories.length.must_equal 0 + end + + it "returns a unique array of categories" do + 5.times do |n| + Product.create :name => "tree#{n}", :price => n, :categories => ["indoor", "halloween", "winter"] + end + + categories = Product.categories + unique_categories = Product.categories.uniq + categories.must_equal unique_categories + end + + it "returns an empty array if there are no products" do + Orderitem.all.destroy_all + Review.all.destroy_all + Product.all.destroy_all + + Product.categories.must_be_kind_of Array + Product.categories.length.must_equal 0 + end + end + + describe "add category method" do + + it "adds to the categories of a product" do + tree = products(:tree1) + before = tree.categories.count + + tree.add_category("spooky") + + tree.categories.count.must_equal (before + 1) + tree.categories.must_include "spooky" + end + + it "will not update the categories if the category is already present" do + tree = products(:tree1) + before = tree.categories.count + + tree.add_category("indoor") + + tree.categories.count.must_equal before + end + end + + describe "self.active_only" do + let(:tree1) {products(:tree1)} + let(:tree2) {products(:tree2)} + + it "returns only products with an active status" do + Product.active_only.pluck(:status).must_include "active" + Product.active_only.pluck(:status).wont_include "retired" + end + end + +end diff --git a/test/models/review_test.rb b/test/models/review_test.rb new file mode 100644 index 0000000000..7353614c79 --- /dev/null +++ b/test/models/review_test.rb @@ -0,0 +1,89 @@ +require "test_helper" + +describe Review do + let(:review) { Review.new } + let(:review1) {reviews(:review1)} + + it "rating must be present" do + review1.valid?.must_equal true + review1.rating = nil + review1.valid?.must_equal false + review1.save + review1.errors.keys.must_include :rating + end + + it "rating must be an integer" do + review1.valid?.must_equal true + review1.rating = 2.3 + review1.valid?.must_equal false + review1.rating = "string" + review1.valid?.must_equal false + end + + it "rating must be <= 1 and >= 5" do + review1.valid?.must_equal true + review1.rating = -1 + review1.valid?.must_equal false + review1.rating = 6 + review1.valid?.must_equal false + end + + it "has a product" do + review1.valid?.must_equal true + review1.product.must_be_kind_of Product + end + + it "must have a product" do + review1.valid?.must_equal true + review1.product = nil + review1.valid?.must_equal false + end + + it "title must be present" do + review1.valid?.must_equal true + review1.title = nil + review1.valid?.must_equal false + review1.save + review1.errors.keys.must_include :title + end + + it "title should be at least 2 characters long" do + review1.valid?.must_equal true + review1.title = "a" + review1.valid?.must_equal false + review1.save + review1.errors.keys.must_include :title + end + + it "title should be at shorter than 100 characters " do + review1.valid?.must_equal true + review1.title = "a" * 101 + review1.valid?.must_equal false + review1.save + review1.errors.keys.must_include :title + end + it "review_text must be present" do + review1.valid?.must_equal true + review1.review_text = nil + review1.valid?.must_equal false + review1.save + review1.errors.keys.must_include :review_text + end + + it "review_text should be at least 2 characters long" do + review1.valid?.must_equal true + review1.review_text = "t" + review1.valid?.must_equal false + review1.save + review1.errors.keys.must_include :review_text + end + + it "review_text should be at shorter than 700 characters " do + review1.valid?.must_equal true + review1.review_text = "n" * 701 + review1.valid?.must_equal false + review1.save + review1.errors.keys.must_include :review_text + end + +end diff --git a/test/system/.keep b/test/system/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 0000000000..42331cbb17 --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,51 @@ +require 'simplecov' +SimpleCov.start 'rails' do + add_filter "/channels/" + add_filter "/jobs/" + add_filter "/mailers/" +end +ENV["RAILS_ENV"] = "test" +require File.expand_path("../../config/environment", __FILE__) +require "rails/test_help" +require "minitest/rails" +require "minitest/reporters" # for Colorized output + +# For colorful output! +Minitest::Reporters.use!( + Minitest::Reporters::SpecReporter.new, + ENV, + Minitest.backtrace_filter +) + + +# To add Capybara feature tests add `gem "minitest-rails-capybara"` +# to the test group in the Gemfile and uncomment the following: +# require "minitest/rails/capybara" + +# Uncomment for awesome colorful output +# require "minitest/pride" + +class ActiveSupport::TestCase + # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. + fixtures :all + def setup + OmniAuth.config.test_mode = true + end + + def login(merchant, provider) + OmniAuth.config.mock_auth[provider] = OmniAuth::AuthHash.new(mock_auth_hash(merchant)) + get auth_callback_path(provider) + + end + + def mock_auth_hash(merchant) + return { + provider: merchant.oauth_provider, + uid: merchant.oauth_uid, + info: { + email: merchant.email, + nickname: merchant.username + } + } + end +end diff --git a/tmp/.keep b/tmp/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vendor/.keep b/vendor/.keep new file mode 100644 index 0000000000..e69de29bb2