diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000..e8321e4963
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,30 @@
+# 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
+
+# Ignore all logfiles and tempfiles.
+/log/*
+/tmp/*
+!/log/.keep
+!/tmp/.keep
+.env
+
+# Ignore uploaded files in development
+/storage/*
+!/storage/.keep
+
+/node_modules
+/yarn-error.log
+
+/public/assets
+.byebug_history
+
+# Ignore master key for decrypting credentials and more.
+/config/master.key
+coverage
+coverage/*
diff --git a/.ruby-version b/.ruby-version
new file mode 100644
index 0000000000..25c81fe399
--- /dev/null
+++ b/.ruby-version
@@ -0,0 +1 @@
+ruby-2.5.1
\ No newline at end of file
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 0000000000..cf3b9cb53f
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,89 @@
+source 'https://rubygems.org'
+git_source(:github) { |repo| "https://github.com/#{repo}.git" }
+
+ruby '2.5.1'
+
+# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
+gem 'rails', '~> 5.2.1'
+# Use postgresql as the database for Active Record
+gem 'pg', '>= 0.18', '< 2.0'
+# Use Puma as the app server
+gem 'puma', '~> 3.11'
+# 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 'mini_racer', 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', '~> 4.0'
+# Use ActiveModel has_secure_password
+# gem 'bcrypt', '~> 3.1.7'
+
+# Use ActiveStorage variant
+# gem 'mini_magick', '~> 4.8'
+
+# Use Capistrano for deployment
+# gem 'capistrano-rails', group: :development
+
+# Reduces boot times through caching; required in config/boot.rb
+gem 'bootsnap', '>= 1.1.0', require: false
+
+gem "omniauth"
+gem "omniauth-github"
+
+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]
+end
+
+group :development do
+ # Access an interactive console on exception pages or by calling '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 'dotenv-rails'
+end
+
+group :test do
+ # Adds support for Capybara system testing and selenium driver
+ gem 'capybara', '>= 2.15'
+ gem 'selenium-webdriver'
+ # Easy installation and use of chromedriver to run system tests with Chrome
+ gem 'chromedriver-helper'
+end
+
+# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
+gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
+
+gem 'jquery-rails'
+gem 'jquery-turbolinks'
+gem 'bootstrap', '~> 4.1.3'
+group :development, :test do
+ gem 'pry-rails'
+end
+
+group :development do
+ gem 'better_errors'
+ gem 'binding_of_caller'
+ gem 'guard'
+ gem 'guard-minitest'
+end
+
+group :test do
+ gem 'minitest-rails'
+ gem 'minitest-reporters'
+end
+
+group :test do
+ gem 'simplecov', require: false
+end
diff --git a/Gemfile.lock b/Gemfile.lock
new file mode 100644
index 0000000000..cffb7c52cf
--- /dev/null
+++ b/Gemfile.lock
@@ -0,0 +1,313 @@
+GEM
+ remote: https://rubygems.org/
+ specs:
+ actioncable (5.2.1)
+ actionpack (= 5.2.1)
+ nio4r (~> 2.0)
+ websocket-driver (>= 0.6.1)
+ actionmailer (5.2.1)
+ actionpack (= 5.2.1)
+ actionview (= 5.2.1)
+ activejob (= 5.2.1)
+ mail (~> 2.5, >= 2.5.4)
+ rails-dom-testing (~> 2.0)
+ actionpack (5.2.1)
+ actionview (= 5.2.1)
+ activesupport (= 5.2.1)
+ rack (~> 2.0)
+ rack-test (>= 0.6.3)
+ rails-dom-testing (~> 2.0)
+ rails-html-sanitizer (~> 1.0, >= 1.0.2)
+ actionview (5.2.1)
+ activesupport (= 5.2.1)
+ builder (~> 3.1)
+ erubi (~> 1.4)
+ rails-dom-testing (~> 2.0)
+ rails-html-sanitizer (~> 1.0, >= 1.0.3)
+ activejob (5.2.1)
+ activesupport (= 5.2.1)
+ globalid (>= 0.3.6)
+ activemodel (5.2.1)
+ activesupport (= 5.2.1)
+ activerecord (5.2.1)
+ activemodel (= 5.2.1)
+ activesupport (= 5.2.1)
+ arel (>= 9.0)
+ activestorage (5.2.1)
+ actionpack (= 5.2.1)
+ activerecord (= 5.2.1)
+ marcel (~> 0.3.1)
+ activesupport (5.2.1)
+ concurrent-ruby (~> 1.0, >= 1.0.2)
+ i18n (>= 0.7, < 2)
+ minitest (~> 5.1)
+ tzinfo (~> 1.1)
+ addressable (2.5.2)
+ public_suffix (>= 2.0.2, < 4.0)
+ ansi (1.5.0)
+ archive-zip (0.11.0)
+ io-like (~> 0.3.0)
+ arel (9.0.0)
+ autoprefixer-rails (9.2.1)
+ execjs
+ better_errors (2.5.0)
+ coderay (>= 1.0.0)
+ erubi (>= 1.0.0)
+ rack (>= 0.9.0)
+ bindex (0.5.0)
+ binding_of_caller (0.8.0)
+ debug_inspector (>= 0.0.1)
+ bootsnap (1.3.2)
+ msgpack (~> 1.0)
+ bootstrap (4.1.3)
+ autoprefixer-rails (>= 6.0.3)
+ popper_js (>= 1.12.9, < 2)
+ sass (>= 3.5.2)
+ builder (3.2.3)
+ byebug (10.0.2)
+ capybara (3.9.0)
+ addressable
+ mini_mime (>= 0.1.3)
+ nokogiri (~> 1.8)
+ rack (>= 1.6.0)
+ rack-test (>= 0.6.3)
+ xpath (~> 3.1)
+ childprocess (0.9.0)
+ ffi (~> 1.0, >= 1.0.11)
+ chromedriver-helper (2.1.0)
+ archive-zip (~> 0.10)
+ nokogiri (~> 1.8)
+ coderay (1.1.2)
+ concurrent-ruby (1.0.5)
+ crass (1.0.4)
+ debug_inspector (0.0.3)
+ docile (1.3.1)
+ dotenv (2.5.0)
+ dotenv-rails (2.5.0)
+ dotenv (= 2.5.0)
+ railties (>= 3.2, < 6.0)
+ erubi (1.7.1)
+ execjs (2.7.0)
+ faraday (0.15.3)
+ multipart-post (>= 1.2, < 3)
+ ffi (1.9.25)
+ formatador (0.2.5)
+ globalid (0.4.1)
+ activesupport (>= 4.2.0)
+ guard (2.14.2)
+ formatador (>= 0.2.4)
+ listen (>= 2.7, < 4.0)
+ lumberjack (>= 1.0.12, < 2.0)
+ nenv (~> 0.1)
+ notiffany (~> 0.0)
+ pry (>= 0.9.12)
+ shellany (~> 0.0)
+ thor (>= 0.18.1)
+ guard-compat (1.2.1)
+ guard-minitest (2.4.6)
+ guard-compat (~> 1.2)
+ minitest (>= 3.0)
+ hashie (3.5.7)
+ i18n (1.1.1)
+ concurrent-ruby (~> 1.0)
+ io-like (0.3.0)
+ jbuilder (2.7.0)
+ activesupport (>= 4.2.0)
+ multi_json (>= 1.2)
+ jquery-rails (4.3.3)
+ rails-dom-testing (>= 1, < 3)
+ railties (>= 4.2.0)
+ thor (>= 0.14, < 2.0)
+ jquery-turbolinks (2.1.0)
+ railties (>= 3.1.0)
+ turbolinks
+ json (2.1.0)
+ jwt (2.1.0)
+ listen (3.1.5)
+ rb-fsevent (~> 0.9, >= 0.9.4)
+ rb-inotify (~> 0.9, >= 0.9.7)
+ ruby_dep (~> 1.2)
+ loofah (2.2.2)
+ crass (~> 1.0.2)
+ nokogiri (>= 1.5.9)
+ lumberjack (1.0.13)
+ mail (2.7.1)
+ mini_mime (>= 0.1.1)
+ marcel (0.3.3)
+ mimemagic (~> 0.3.2)
+ method_source (0.9.0)
+ mimemagic (0.3.2)
+ mini_mime (1.0.1)
+ mini_portile2 (2.3.0)
+ minitest (5.11.3)
+ minitest-rails (3.0.0)
+ minitest (~> 5.8)
+ railties (~> 5.0)
+ minitest-reporters (1.3.5)
+ ansi
+ builder
+ minitest (>= 5.0)
+ ruby-progressbar
+ msgpack (1.2.4)
+ multi_json (1.13.1)
+ multi_xml (0.6.0)
+ multipart-post (2.0.0)
+ nenv (0.3.0)
+ nio4r (2.3.1)
+ nokogiri (1.8.5)
+ mini_portile2 (~> 2.3.0)
+ notiffany (0.1.1)
+ nenv (~> 0.1)
+ shellany (~> 0.0)
+ oauth2 (1.4.1)
+ faraday (>= 0.8, < 0.16.0)
+ jwt (>= 1.0, < 3.0)
+ multi_json (~> 1.3)
+ multi_xml (~> 0.5)
+ rack (>= 1.2, < 3)
+ omniauth (1.8.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.5.0)
+ oauth2 (~> 1.1)
+ omniauth (~> 1.2)
+ pg (1.1.3)
+ popper_js (1.14.3)
+ pry (0.11.3)
+ coderay (~> 1.1.0)
+ method_source (~> 0.9.0)
+ pry-rails (0.3.6)
+ pry (>= 0.10.4)
+ public_suffix (3.0.3)
+ puma (3.12.0)
+ rack (2.0.5)
+ rack-test (1.1.0)
+ rack (>= 1.0, < 3)
+ rails (5.2.1)
+ actioncable (= 5.2.1)
+ actionmailer (= 5.2.1)
+ actionpack (= 5.2.1)
+ actionview (= 5.2.1)
+ activejob (= 5.2.1)
+ activemodel (= 5.2.1)
+ activerecord (= 5.2.1)
+ activestorage (= 5.2.1)
+ activesupport (= 5.2.1)
+ bundler (>= 1.3.0)
+ railties (= 5.2.1)
+ sprockets-rails (>= 2.0.0)
+ rails-dom-testing (2.0.3)
+ activesupport (>= 4.2.0)
+ nokogiri (>= 1.6)
+ rails-html-sanitizer (1.0.4)
+ loofah (~> 2.2, >= 2.2.2)
+ railties (5.2.1)
+ actionpack (= 5.2.1)
+ activesupport (= 5.2.1)
+ method_source
+ rake (>= 0.8.7)
+ thor (>= 0.19.0, < 2.0)
+ rake (12.3.1)
+ rb-fsevent (0.10.3)
+ rb-inotify (0.9.10)
+ ffi (>= 0.5.0, < 2)
+ ruby-progressbar (1.10.0)
+ ruby_dep (1.5.0)
+ rubyzip (1.2.2)
+ sass (3.6.0)
+ sass-listen (~> 4.0.0)
+ sass-listen (4.0.0)
+ rb-fsevent (~> 0.9, >= 0.9.4)
+ rb-inotify (~> 0.9, >= 0.9.7)
+ sass-rails (5.0.7)
+ 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.14.1)
+ childprocess (~> 0.5)
+ rubyzip (~> 1.2, >= 1.2.2)
+ shellany (0.0.1)
+ simplecov (0.16.1)
+ docile (~> 1.1)
+ 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.2)
+ concurrent-ruby (~> 1.0)
+ rack (> 1, < 3)
+ sprockets-rails (3.2.1)
+ actionpack (>= 4.0)
+ activesupport (>= 4.0)
+ sprockets (>= 3.0.0)
+ thor (0.20.0)
+ thread_safe (0.3.6)
+ tilt (2.0.8)
+ turbolinks (5.2.0)
+ turbolinks-source (~> 5.2)
+ turbolinks-source (5.2.0)
+ tzinfo (1.2.5)
+ thread_safe (~> 0.1)
+ uglifier (4.1.19)
+ execjs (>= 0.3.0, < 3)
+ web-console (3.7.0)
+ actionview (>= 5.0)
+ activemodel (>= 5.0)
+ bindex (>= 0.4.0)
+ railties (>= 5.0)
+ websocket-driver (0.7.0)
+ websocket-extensions (>= 0.1.0)
+ websocket-extensions (0.1.3)
+ xpath (3.2.0)
+ nokogiri (~> 1.8)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ better_errors
+ binding_of_caller
+ bootsnap (>= 1.1.0)
+ bootstrap (~> 4.1.3)
+ byebug
+ capybara (>= 2.15)
+ chromedriver-helper
+ dotenv-rails
+ guard
+ guard-minitest
+ jbuilder (~> 2.5)
+ jquery-rails
+ jquery-turbolinks
+ listen (>= 3.0.5, < 3.2)
+ minitest-rails
+ minitest-reporters
+ omniauth
+ omniauth-github
+ pg (>= 0.18, < 2.0)
+ pry-rails
+ puma (~> 3.11)
+ rails (~> 5.2.1)
+ sass-rails (~> 5.0)
+ selenium-webdriver
+ simplecov
+ spring
+ spring-watcher-listen (~> 2.0.0)
+ turbolinks (~> 5)
+ tzinfo-data
+ uglifier (>= 1.3.0)
+ web-console (>= 3.3.0)
+
+RUBY VERSION
+ ruby 2.5.1p57
+
+BUNDLED WITH
+ 1.16.2
diff --git a/Guardfile b/Guardfile
new file mode 100644
index 0000000000..e34f706f4a
--- /dev/null
+++ b/Guardfile
@@ -0,0 +1,9 @@
+guard :minitest, autorun: false, spring: true do
+ 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' }
+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/.DS_Store b/app/assets/.DS_Store
new file mode 100644
index 0000000000..825ffff15c
Binary files /dev/null and b/app/assets/.DS_Store differ
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/bird.jpg b/app/assets/images/bird.jpg
new file mode 100644
index 0000000000..7a01e30d7a
Binary files /dev/null and b/app/assets/images/bird.jpg differ
diff --git a/app/assets/images/dog.jpg b/app/assets/images/dog.jpg
new file mode 100644
index 0000000000..e5c1ec7273
Binary files /dev/null and b/app/assets/images/dog.jpg differ
diff --git a/app/assets/images/favicon.png b/app/assets/images/favicon.png
new file mode 100644
index 0000000000..a4dac32698
Binary files /dev/null and b/app/assets/images/favicon.png differ
diff --git a/app/assets/images/hanging-dog.jpg b/app/assets/images/hanging-dog.jpg
new file mode 100644
index 0000000000..1ddf04262c
Binary files /dev/null and b/app/assets/images/hanging-dog.jpg differ
diff --git a/app/assets/images/shopping-bag-icon-png-17.jpg b/app/assets/images/shopping-bag-icon-png-17.jpg
new file mode 100644
index 0000000000..70b21fea41
Binary files /dev/null and b/app/assets/images/shopping-bag-icon-png-17.jpg differ
diff --git a/app/assets/images/shopping-bag.png b/app/assets/images/shopping-bag.png
new file mode 100644
index 0000000000..b96b398937
Binary files /dev/null and b/app/assets/images/shopping-bag.png differ
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
new file mode 100644
index 0000000000..4f73c21a7d
--- /dev/null
+++ b/app/assets/javascripts/application.js
@@ -0,0 +1,20 @@
+// 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 jquery3
+ //= require popper
+ //= require bootstrap-sprockets
+
+//
+//= require rails-ujs
+//= require activestorage
+//= require turbolinks
+//= require_tree .
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/categories.js b/app/assets/javascripts/categories.js
new file mode 100644
index 0000000000..dee720facd
--- /dev/null
+++ b/app/assets/javascripts/categories.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/channels/.keep b/app/assets/javascripts/channels/.keep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/app/assets/javascripts/orderproducts.js b/app/assets/javascripts/orderproducts.js
new file mode 100644
index 0000000000..dee720facd
--- /dev/null
+++ b/app/assets/javascripts/orderproducts.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/orders_products.js b/app/assets/javascripts/orders_products.js
new file mode 100644
index 0000000000..dee720facd
--- /dev/null
+++ b/app/assets/javascripts/orders_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/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/application.scss b/app/assets/stylesheets/application.scss
new file mode 100644
index 0000000000..e528511090
--- /dev/null
+++ b/app/assets/stylesheets/application.scss
@@ -0,0 +1,353 @@
+/*
+* 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.
+*
+*/
+
+/* Custom bootstrap variables must be set or imported *before* bootstrap. */
+@import "bootstrap";
+/* Import scss content */
+@import "**/*";
+@import url("https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css");
+@import url('https://fonts.googleapis.com/css?family=Kirang+Haerang|Muli|Niramit|Inconsolata|Indie+Flower|KoHo|Rubik');
+
+body {
+ font-family: 'Muli', sans-serif;
+}
+
+a {
+ color: black;
+}
+
+a:visited {
+ color: black;
+}
+
+h1 {
+ text-align: center;
+ color: grey;
+ font-size: 2.5em;
+}
+
+h3 {
+ font-size: 1em;
+}
+
+h4 {
+ font-size: 1em;
+}
+
+h2 {
+ padding-bottom: 1em;
+}
+
+li {
+ list-style: none;
+}
+
+ul {
+ list-style: none;
+ margin-bottom: 0px;
+}
+
+#add_new_creature {
+ // padding-top: 1em;
+}
+
+
+#new-creature-form {
+ margin: 0%;
+}
+
+.header {
+ display: block;
+ margin: 0px;
+ padding: 0px;
+ // padding-bottom: 2rem;
+}
+
+.logo a:hover {
+ text-decoration: none;
+}
+
+.logo a:visited {
+ color: #FF8514;
+}
+
+header a {
+ color: gray;
+}
+
+header a:hover {
+ color: #FF8514;
+}
+
+nav li:first-child {
+ padding-right: 20px;
+}
+
+.nav-container {
+ background-color: white;
+}
+
+.header_container .container {
+ max-width: 100%;
+}
+
+.order-text {
+ padding: 1em;
+}
+
+.logo {
+ padding-top: 15px;
+ display: block;
+ padding-left: 1.5em;
+ position: relative;
+ box-sizing: border-box;
+ font-size: 4.5em;
+ color: #FF8514;
+ font-family: 'Indie Flower', cursive;
+}
+
+.form-control {
+ border: 1px solid #FF8514;
+ box-shadow: 0 0 0 1px #FF8514;
+}
+
+.search-order {
+ padding-left: 15px;
+}
+
+.bottom-nav {
+ position: relative;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.cart-icon {
+ padding-right: 1em;
+}
+
+.cart-icon img {
+ height: 27px;
+ position: relative;
+}
+
+.top-nav {
+ display: flex;
+ flex-wrap: wrap;
+ width: 100%;
+ height: 80px;
+ justify-content: center;
+ align-items: center;
+ position: relative;
+ background-color: #F9F3E6;
+}
+
+.btn-outline-primary {
+ border-color: #FF8514;
+ margin: 1em;
+ color: #FF8514;
+}
+
+.btn-outline-primary:hover {
+ background-color: #FF8514;
+}
+
+.card-title a {
+ color: black;
+
+}
+
+.card-title a:hover {
+ color: #FF8514;
+ list-style: none;
+}
+
+.main-content {
+ margin-left: 10%;
+ margin-right:10%;
+
+}
+
+
+#add-category {
+ margin-left: 10%;
+ margin-right:10%;
+ margin-top: 0;
+}
+
+.main-container {
+ margin: 0%;
+}
+
+#main-img {
+ object-fit: none;
+
+ img {
+ width: 80%;
+ height: 50%;
+ }
+}
+
+.all-products {
+ // margin-top: 8%;
+}
+
+.product-content {
+ // margin-top: 10%
+}
+
+.user-login {
+ margin-left: auto;
+ padding-right: 2rem;
+}
+
+.cart-img {
+ width: 150px;
+ height: auto;
+}
+
+.alert {
+ margin-top: 1%;
+}
+
+.card-group-container {
+ display: flex;
+ justify-content: space-around;
+ flex-wrap: wrap;
+ padding: 3em;
+}
+
+.card-container {
+ margin: 1em;
+}
+
+.checked {
+ color: black;
+}
+
+.notfound {
+ text-align: center;
+ align-self: center;
+ width: 70%;
+}
+
+.not_found {
+ font-size: 4rem;
+}
+
+.product-show-container {
+ display: grid;
+ grid-template: auto auto / 1fr 1fr;
+ justify-items: center;
+}
+
+.user-products-img img{
+ width: 200px;
+ height: auto;
+ padding: 1em;
+}
+
+.user-products-flex {
+ display: flex;
+ flex-wrap: wrap;
+ box-sizing: border-box;
+ flex-direction: row;
+ justify-content: center;
+}
+
+.product-show-img-text {
+ grid-row: 1 / 1;
+ grid-column: 1 / 3;
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-evenly;
+ border-bottom: solid lightgray;
+ padding-bottom: 3em;
+}
+
+.product-description {
+ padding-left: 2em;
+}
+
+.product-show-img img{
+ width: auto;
+ height: 300px;
+}
+
+.user-products-container, .reviews-container {
+ padding-top: 2em;
+}
+
+.reviews-container {
+ grid-row: 2 / 3;
+ grid-column: 1 / 2;
+ align-items: stretch;
+ padding-left: 10%;
+
+}
+
+.user-products-container {
+ grid-row: 2 / 3;
+ grid-column: 2 / 3;
+}
+
+.reviews-only-container, .only-user-products-container, .no-reviews-no-user-products {
+ grid-row: 2 / 3;
+ grid-column: 1 / 3;
+ padding-top: 2em;
+}
+
+.reviews ul {
+ list-style: none;
+ border-bottom: solid lightgray;
+ padding-bottom: 1em;
+ padding-top: 1em;
+}
+
+.cart-heading-padding {
+ padding-bottom: 1em;
+}
+
+.order_types {
+ float: right;
+}
+
+.orders_details {
+ float: right;
+ clear: both;
+ text-align: right;
+}
+
+.order_heading {
+ display: inline-block;
+}
+
+.more-from, .review-button {
+ text-align: center;
+ padding-top: 0.5em;
+}
+
+.parent-name {
+ padding-top: 2em;
+ padding-left: 3em;
+}
+
+label {
+ padding-right: 10px;
+ padding-top: 10px;
+}
+
+.form-check-label {
+
+}
diff --git a/app/assets/stylesheets/categories.scss b/app/assets/stylesheets/categories.scss
new file mode 100644
index 0000000000..9f5cb35511
--- /dev/null
+++ b/app/assets/stylesheets/categories.scss
@@ -0,0 +1,6 @@
+// Place all the styles related to the categories controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
+#add-category {
+ margin: 10%;
+}
diff --git a/app/assets/stylesheets/home.scss b/app/assets/stylesheets/home.scss
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/app/assets/stylesheets/orderproducts.scss b/app/assets/stylesheets/orderproducts.scss
new file mode 100644
index 0000000000..0dc6e76eaa
--- /dev/null
+++ b/app/assets/stylesheets/orderproducts.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the orderproducts 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..741506954d
--- /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/orders_products.scss b/app/assets/stylesheets/orders_products.scss
new file mode 100644
index 0000000000..84ea664e8a
--- /dev/null
+++ b/app/assets/stylesheets/orders_products.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the OrdersProducts 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..892312fc37
--- /dev/null
+++ b/app/assets/stylesheets/products.scss
@@ -0,0 +1,6 @@
+// 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/
+#new-creature-form {
+ margin: 10%;
+}
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..2813db6e99
--- /dev/null
+++ b/app/controllers/application_controller.rb
@@ -0,0 +1,60 @@
+class ApplicationController < ActionController::Base
+ before_action :current_user
+ before_action :find_user
+ before_action :build_cart
+ before_action :all_users
+ before_action :all_categories
+
+ helper_method :logged_in?
+ helper_method :current_user
+
+
+
+ private
+ def build_cart
+ session[:cart] = Array.new if !session[:cart]
+ if session[:cart]
+ session[:cart].each.with_index do |hash, index|
+ hash.each do |id, quantity|
+ product = Product.find_by(id: id)
+ if (quantity > product.stock_count) && (product.stock_count > 0)
+ session[:cart][index][id] = product.stock_count
+ flash[:warning] = "#{product.name.capitalize} has been updated due to a change in availability."
+ redirect_back(fallback_location: cart_path)
+ elsif (quantity > product.stock_count) && (product.stock_count == 0)
+ session[:cart].delete_at(index)
+ flash[:danger] = "#{product.name.capitalize} has been removed from your basket due because it is no longer available."
+ redirect_back(fallback_location: root_path)
+ end
+ end
+ end
+ end
+ end
+
+ def logged_in?
+ current_user.present?
+ end
+
+ def current_user
+ @current_user ||= User.find_by(id: session[:user_id])
+ end
+
+ def find_user
+ @user = User.find_by(id: params[:id])
+ end
+
+ def require_login
+ if @current_user.nil?
+ flash[:danger] = "Sorry, the fulfillment page is only for creature rescuers."
+ redirect_to root_path
+ end
+ end
+
+ def all_users
+ @users = User.all
+ end
+
+ def all_categories
+ @categories = Category.all
+ end
+end
diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb
new file mode 100644
index 0000000000..bacbffa378
--- /dev/null
+++ b/app/controllers/categories_controller.rb
@@ -0,0 +1,43 @@
+class CategoriesController < ApplicationController
+before_action :find_category, only: [:show, :destroy]
+before_action :require_login, only: [:new]
+
+
+ def index
+ @categories = Category.category_list
+ end
+
+ def show; end
+
+ def new
+ @category = Category.new
+ end
+
+ def create
+ @products = Product.new
+ @category = Category.new(category_params)
+
+ if @category.save
+ # redirect_to '/books'
+ flash[:success] = "Category created successfully."
+ redirect_to categories_path
+ else
+ # Validations failed! What do we do?
+ # This flash message is redundant but for demonstration purposes
+ flash.now[:danger] = "No category created."
+ render :new, status: :bad_request
+ end
+ end
+
+private
+ def find_category
+ @category = Category.find_by(id: params[:id])
+
+ head :not_found unless @category
+ end
+
+ def category_params
+ params.require(:category).permit(:name, product_ids: [])
+ 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/orderproducts_controller.rb b/app/controllers/orderproducts_controller.rb
new file mode 100644
index 0000000000..00048e2b03
--- /dev/null
+++ b/app/controllers/orderproducts_controller.rb
@@ -0,0 +1,21 @@
+class OrderproductsController < ApplicationController
+ before_action :find_orderproduct
+
+ def update
+ @orderproduct.status = params[:orderproduct][:status]
+ if @orderproduct.save
+ flash[:success] = "Status of requested creature has been changed."
+ redirect_back(fallback_location: "get_orders")
+ else
+ flash[:warning] = "Status could not be changed."
+ redirect_back(fallback_location: "get_orders")
+ end
+ end
+
+ private
+
+ def find_orderproduct
+ @orderproduct = Orderproduct.find_by(id: params[:id])
+ end
+
+end
diff --git a/app/controllers/orders_controller.rb b/app/controllers/orders_controller.rb
new file mode 100644
index 0000000000..5aa8184ca4
--- /dev/null
+++ b/app/controllers/orders_controller.rb
@@ -0,0 +1,123 @@
+class OrdersController < ApplicationController
+ before_action :find_order
+ skip_before_action :find_order, only: [:fulfillment, :paid, :new, :create, :completed, :cancelled]
+ before_action :require_login, only: [:fulfillment, :paid, :completed, :cancelled]
+
+ def new
+ @order = Order.new
+ end
+
+ def show; end
+
+ def create
+ @order = Order.new
+ @order.status = "pending"
+ @order.save
+
+ Orderproduct.create_product_orders(@order.id, session[:cart])
+ if session[:cart].length != @order.orderproducts.length
+ @order.orderproducts.each do |orderproduct|
+ orderproduct.destroy
+ end
+ flash[:danger] = 'Unable to complete adoption request, creature not available.'
+ render :new, status: :bad_request
+ else
+ @order.total_cost = @order.order_total
+ @order.update(order_params)
+ if @order.save
+ @order.reduce_stock
+ @order.status = "paid"
+ @order.save
+ flash[:success] = "Adoption request successfully placed! (Order ##{@order.id})"
+ session[:cart] = nil
+ redirect_to order_path(@order.id)
+ else
+ @order.destroy
+ flash.now[:messages] = @order.errors.messages
+ render :new, status: :bad_request
+ end
+ end
+ end
+
+
+ def update
+ @order.update(order_params)
+ if @order && @order.save
+ flash[:success] = 'Status has been changed.'
+ redirect_to order_path(@order.id)
+ else
+ flash[:danger] = 'Adoption request was not updated.'
+ redirect_to order_path(@order.id)
+ end
+ end
+
+ def fulfillment
+ if @current_user
+ @orderproducts = Order.find_orderproducts(@current_user, nil)
+ @total_revenue = Order.products_sold_total(@current_user, @orderproducts)
+ @count = Order.count_orders(@current_user, nil)
+ else
+ flash[:danger] = 'Sorry, the fulfillment page is only for creature rescuers.'
+ redirect_to root_path
+ end
+ end
+
+ def paid
+ if @current_user
+ @orderproducts = Order.find_orderproducts(@current_user, "paid")
+ @total_revenue = Order.products_sold_total(@current_user, @orderproducts)
+ @count = Order.count_orders(@current_user, "paid")
+ else
+ flash[:danger] = 'Sorry, the fulfillment page is only for creature rescuers.'
+ redirect_to root_path
+ end
+ end
+
+ def completed
+ if @current_user
+ @orderproducts = Order.find_orderproducts(@current_user, "completed")
+ @total_revenue = Order.products_sold_total(@current_user, @orderproducts)
+ @count = Order.count_orders(@current_user, "completed")
+ else
+ flash[:danger] = 'Sorry, the fulfillment page is only for creature rescuers.'
+ redirect_to root_path
+ end
+ end
+
+ def cancelled
+ if @current_user
+ @orderproducts = Order.find_orderproducts(@current_user, "cancelled")
+ @total_revenue = Order.products_sold_total(@current_user, @orderproducts)
+ @count = Order.count_orders(@current_user, "cancelled")
+ else
+ flash[:danger] = 'Sorry, the fulfillment page is only for creature rescuers.'
+ redirect_to root_path
+ end
+ end
+
+ def search
+ if @order
+ redirect_to order_path(@order)
+ else
+ flash[:danger] = "Adoption request #{params[:id]} does not exist"
+ redirect_back fallback_location: root_path
+ end
+ end
+
+
+ private
+
+ def find_order
+ @order = Order.find_by(id: params[:id].to_i)
+
+ if @order.nil?
+ flash.now[:danger] = "Cannot find adoption request ##{params[:id]}"
+ render :notfound, status: :not_found
+ end
+ end
+
+ def order_params
+ return params.require(:order).permit(:name, :email, :mailing_address, :zip_code, :cc_number, :cc_expiration, :cc_cvv, :total_cost, :status)
+ end
+
+end
diff --git a/app/controllers/products_controller.rb b/app/controllers/products_controller.rb
new file mode 100644
index 0000000000..8cbd247783
--- /dev/null
+++ b/app/controllers/products_controller.rb
@@ -0,0 +1,62 @@
+class ProductsController < ApplicationController
+ before_action :find_product, except: [:index, :cart_view, :new, :create]
+ before_action :require_product_owner, only: [:edit, :update, :destroy]
+ #skip_before_action :find_product, only: [:index, :cart_view, :new, :create, :home]
+ before_action :require_login, only: [:new]
+
+ def index
+ @products = Product.order(:name)
+ end
+
+ def new
+ @product = Product.new
+ end
+
+ def create
+ @product = Product.new(product_params)
+ if @product.save
+ flash[:success] = "New creature added!"
+ redirect_to product_path(@product.id)
+ else
+ flash.now[:messages] = @product.errors.messages
+ render :new, status: :bad_request
+ end
+ end
+
+ def show;end
+
+ def edit;end
+
+ def update
+ @product.update(product_params)
+ if @product.save
+ flash[:success] = "Successfully updated creature."
+ redirect_to product_path(@product)
+ else
+ render :edit, :status => :bad_request
+ end
+ end
+
+ private
+ def require_product_owner
+ if @current_user != @product.user
+ flash[:danger] = "Cannot edit another rescuer's creatures"
+ redirect_to root_path
+ end
+ end
+
+ def product_params
+ return params.require(:product).permit(:name, :price, :stock_count, :user_id, :photo_url, :description, category_ids: [])
+ end
+
+ def find_product
+ @product = Product.find_by(id: params[:id])
+ if !@product
+ @product = Product.find_by(id: params[:product_id])
+ end
+ if @product.nil?
+ render :notfound, status: :not_found
+ end
+ end
+
+end
diff --git a/app/controllers/reviews_controller.rb b/app/controllers/reviews_controller.rb
new file mode 100644
index 0000000000..bc5e422fbe
--- /dev/null
+++ b/app/controllers/reviews_controller.rb
@@ -0,0 +1,33 @@
+class ReviewsController < ApplicationController
+ before_action :cant_leave_review_for_own_product, only: [:new, :create]
+
+ def new
+ @review = Review.new
+ @product = Product.find_by(id: params[:product_id])
+ end
+
+ def create
+ @review = Review.new(review_params)
+ if @review.save
+ flash[:success] = "Thanks for leaving a review!"
+ redirect_to product_path(@review.product_id)
+ else
+ flash[:warning] = "Review unsuccessful."
+ redirect_to product_path(@review.product_id)
+ end
+ end
+
+ private
+ def review_params
+ params.require(:review).permit(:name, :rating, :review, :product_id)
+ end
+
+ def cant_leave_review_for_own_product
+ user = Product.find_by(id: params[:product_id]).user
+ if @current_user == user
+ flash[:warning] = "You cannot review your own creatures!"
+ redirect_to product_path(params[:product_id])
+ end
+ end
+
+end
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
new file mode 100644
index 0000000000..9afe2183c7
--- /dev/null
+++ b/app/controllers/sessions_controller.rb
@@ -0,0 +1,110 @@
+class SessionsController < ApplicationController
+ before_action :find_product, only: [:add_to_cart, :update_quantity, :remove_from_cart]
+
+ def create
+ auth_hash = request.env['omniauth.auth']
+
+ user = User.find_by(uid: auth_hash[:uid], provider: 'github') ||
+ User.create_from_github(auth_hash)
+
+ if user.save
+ flash[:success] = "Logged in as returning user #{user.name}"
+ session[:user_id] = user.id
+ redirect_to root_path
+ else
+ flash[:danger] = "Could not create new user account: #{user.errors.messages}. Check github email and name please"
+ redirect_back fallback_location: root_path
+ end
+ end
+
+ def destroy
+ session[:user_id] = nil
+ flash[:success] = 'Successfully logged out'
+ redirect_back fallback_location: root_path
+ end
+
+ def add_to_cart
+ id = @product.id.to_i
+ quantity = params[:quantity].to_i
+ # session[:cart] = nil
+ item = false
+ if [*1..@product.stock_count].include? (quantity)
+ session[:cart].each.with_index do |hash, index|
+ hash.each do |key, value|
+ if key == id.to_s
+ item = true
+ new_quantity = value + quantity
+ if new_quantity <= @product.stock_count
+ session[:cart][index][key] = value + quantity
+ flash[:success] = "Added to basket"
+ else
+ flash[:warning] = "Failure to add to basket. Not enough creatures available."
+ end
+ end
+ end
+ end
+ if item == false
+ session[:cart] << { id => quantity}
+ flash[:success] = "Added to basket"
+ end
+ else
+ flash[:warning] = "Failure to add to basket. Not enough creatures available."
+ end
+ redirect_back(fallback_location: root_path)
+ end
+
+ def cart_view
+ @cart_items = []
+ session[:cart].each.with_index do |hash|
+ hash.each do |key, value|
+ cart_product = Product.find_by(id: key.to_i)
+ @cart_items << [cart_product, value]
+ end
+ end
+ render :cart
+ end
+
+ def update_quantity
+ id = @product.id.to_i
+ quantity = params[:quantity].to_i
+ if [*1..@product.stock_count].include? (quantity)
+ session[:cart].each.with_index do |hash, index|
+ hash.each do |key, value|
+ if key == id.to_s
+ session[:cart][index][key] = quantity
+ flash[:success] = "Successfully updated basket."
+ end
+ end
+ end
+ else
+ flash[:warning] = "Failure to add to basket. Invalid availability."
+ end
+ redirect_to cart_path
+ end
+
+ def remove_from_cart
+ id = @product.id.to_i
+ session[:cart].each.with_index do |hash, index|
+ hash.each do |key, value|
+ if key == id.to_s
+ session[:cart].delete_at(index)
+ flash[:success] = "Successfully removed from basket."
+ redirect_back(fallback_location: root_path)
+ end
+ end
+ end
+ end
+
+ private
+
+ def find_product
+ @product = Product.find_by(id: params[:id])
+ if !@product
+ @product = Product.find_by(id: params[:product_id])
+ end
+ if @product.nil?
+ render :notfound, status: :not_found
+ end
+ end
+
+end
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
new file mode 100644
index 0000000000..5c0ccc8d9b
--- /dev/null
+++ b/app/controllers/users_controller.rb
@@ -0,0 +1,12 @@
+class UsersController < ApplicationController
+ before_action :find_user, only: [:show]
+
+ def index
+ @users = User.all
+ end
+
+ def show
+ render :notfound, status: :not_found unless @user
+ end
+
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
new file mode 100644
index 0000000000..3be592d550
--- /dev/null
+++ b/app/helpers/application_helper.rb
@@ -0,0 +1,12 @@
+module ApplicationHelper
+
+ def readable_date(date)
+ ("" + date.strftime("%b %d, %Y") + "").html_safe
+ end
+
+ def money_display(money)
+ ("$" + '%.2f' % money + "").html_safe
+ end
+
+
+end
diff --git a/app/helpers/categories_helper.rb b/app/helpers/categories_helper.rb
new file mode 100644
index 0000000000..e06f31554c
--- /dev/null
+++ b/app/helpers/categories_helper.rb
@@ -0,0 +1,2 @@
+module CategoriesHelper
+end
diff --git a/app/helpers/orderproducts_helper.rb b/app/helpers/orderproducts_helper.rb
new file mode 100644
index 0000000000..9ec91f4e16
--- /dev/null
+++ b/app/helpers/orderproducts_helper.rb
@@ -0,0 +1,2 @@
+module OrderproductsHelper
+end
diff --git a/app/helpers/orders_helper.rb b/app/helpers/orders_helper.rb
new file mode 100644
index 0000000000..443227fd48
--- /dev/null
+++ b/app/helpers/orders_helper.rb
@@ -0,0 +1,2 @@
+module OrdersHelper
+end
diff --git a/app/helpers/orders_products_helper.rb b/app/helpers/orders_products_helper.rb
new file mode 100644
index 0000000000..159322c253
--- /dev/null
+++ b/app/helpers/orders_products_helper.rb
@@ -0,0 +1,2 @@
+module OrdersProductsHelper
+end
diff --git a/app/helpers/products_helper.rb b/app/helpers/products_helper.rb
new file mode 100644
index 0000000000..ab5c42b325
--- /dev/null
+++ b/app/helpers/products_helper.rb
@@ -0,0 +1,2 @@
+module ProductsHelper
+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..0e0f662d41
--- /dev/null
+++ b/app/models/application_record.rb
@@ -0,0 +1,4 @@
+class ApplicationRecord < ActiveRecord::Base
+ self.abstract_class = true
+
+end
diff --git a/app/models/category.rb b/app/models/category.rb
new file mode 100644
index 0000000000..5da9d94836
--- /dev/null
+++ b/app/models/category.rb
@@ -0,0 +1,15 @@
+class Category < ApplicationRecord
+ has_many :products
+ validates :name, presence: true, uniqueness: true
+
+ has_and_belongs_to_many :products
+
+ def self.category_list
+ category_list = []
+ Category.all.each do |cat|
+ category_list << cat.name
+ end
+ category_list
+ end
+
+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/order.rb b/app/models/order.rb
new file mode 100644
index 0000000000..e9765fff34
--- /dev/null
+++ b/app/models/order.rb
@@ -0,0 +1,68 @@
+class Order < ApplicationRecord
+ has_many :orderproducts, dependent: :destroy
+ validates :cc_number, :cc_cvv, :total_cost, :zip_code, numericality: true, on: :update
+
+ validates :name, :email, :mailing_address, :zip_code, :cc_number,
+ :cc_expiration, :cc_cvv, :status, :total_cost, presence: true, on: :update
+
+ validates :orderproducts, :length => { :minimum => 1 }, on: :update
+
+ def order_total
+ total_cost = 0
+ self.orderproducts.each do |orderproduct|
+ total_cost += (orderproduct.product.price * orderproduct.quantity)
+ end
+ total_cost
+ end
+
+ def reduce_stock
+ self.orderproducts.each do |item|
+ Product.adjust_stock_count(item.product_id, item.quantity)
+ end
+ end
+
+ def self.find_orderproducts(user, status)
+ orderproducts = []
+ Order.all.each do |order|
+ if order.status == status || status == nil
+ order.orderproducts.each do |item|
+ product = item.product
+ if user.id == product.user_id
+ orderproducts << item
+ end
+ end
+ end
+ end
+ orderproducts
+ end
+
+ def self.count_orders(user, status)
+ count = 0
+ Order.all.each do |order|
+ if order.status == status || status == nil
+ order.orderproducts.each do |item|
+ product = item.product
+ if user.id == product.user_id
+ count += 1
+ end
+ end
+ end
+ end
+ count
+ end
+
+ def self.products_sold_total(user, orderproducts)
+ total_revenue = 0
+ orderproducts.each do |item|
+ product = item.product
+ if user.id == product.user_id
+ per_unit_cost = item.product.price
+ total_revenue += item.quantity * per_unit_cost
+ end
+ end
+ total_revenue
+ end
+
+
+
+end
diff --git a/app/models/orderproduct.rb b/app/models/orderproduct.rb
new file mode 100644
index 0000000000..c1e75a61db
--- /dev/null
+++ b/app/models/orderproduct.rb
@@ -0,0 +1,33 @@
+class Orderproduct < ApplicationRecord
+ belongs_to :order
+ belongs_to :product
+
+ STATUSES= %w(pending shipped)
+ validates :status, presence: true, inclusion: { in: STATUSES }
+ validate :quantity_in_stock?, on: :create
+ validates :quantity, presence: true, numericality: { only_integer: true, greater_than: 0 }
+ validates :product_id, presence: true
+ validates :order_id, presence: true
+
+
+ def self.create_product_orders(order_id, session)
+ session.each do |item|
+ item.each do |key, value|
+ if value <= Product.find_by(id: key.to_i).stock_count
+ Orderproduct.create(product_id: key.to_i, quantity: value, order_id: order_id, status: "pending")
+ end
+ end
+ end
+ end
+
+ private
+
+ def quantity_in_stock?
+ if self.product && self.quantity
+ if self.quantity > self.product.stock_count
+ errors.add(:quantity, "Quantity must be available.")
+ end
+ end
+ end
+
+end
diff --git a/app/models/product.rb b/app/models/product.rb
new file mode 100644
index 0000000000..b8912ce5c8
--- /dev/null
+++ b/app/models/product.rb
@@ -0,0 +1,45 @@
+class Product < ApplicationRecord
+ belongs_to :user
+ has_many :reviews
+ has_many :orderproducts
+ has_and_belongs_to_many :categories
+ validates :name, :price, presence: true
+ validates :name, uniqueness: true
+ validates :price, presence: true, numericality: { greater_than: 0 }
+ validates_format_of :photo_url, :with => /\A[http]/
+
+
+ def self.adjust_stock_count(product_id, count_sold)
+ product = Product.find(product_id)
+ reduced_stock = product.stock_count - count_sold
+ product.update(stock_count: reduced_stock)
+ product.save
+ end
+
+ def average_rating
+ rating = 0
+ sum = self.reviews.reduce(0) do |sum, review|
+ sum += review.rating
+ end
+ rating = sum / self.reviews.length if self.reviews.length > 0
+ return rating
+ end
+
+ def in_cart?(session)
+ session.each do |item|
+ item.each do |key, value|
+ return true if key.to_i == self.id
+ end
+ end
+ return false
+ end
+
+ def cart_adjust_quantity(session)
+ session.each do |item|
+ item.each do |id, quantity|
+ return self.stock_count - quantity if id.to_i == self.id
+ end
+ end
+ end
+
+end
diff --git a/app/models/review.rb b/app/models/review.rb
new file mode 100644
index 0000000000..0257fc69dc
--- /dev/null
+++ b/app/models/review.rb
@@ -0,0 +1,10 @@
+class Review < ApplicationRecord
+ belongs_to :product
+
+ validates :rating, presence: true, numericality: { only_integer: true }
+ validates_inclusion_of :rating, :in => [ 1, 2, 3, 4, 5 ]
+ validates :name, presence: true
+ validates :review, presence: true
+ validates :product_id, presence: true
+
+end
diff --git a/app/models/user.rb b/app/models/user.rb
new file mode 100644
index 0000000000..aa394f712e
--- /dev/null
+++ b/app/models/user.rb
@@ -0,0 +1,25 @@
+class User < ApplicationRecord
+ has_many :products
+ has_many :orderproducts, :through => :products
+ has_many :orders, :through => :orderproducts
+
+ validates :name, uniqueness: true, presence: true
+ validates :email, uniqueness: true, presence: true
+
+ def self.build_from_github(auth_hash)
+ User.new(
+ uid: auth_hash[:uid],
+ provider: 'github',
+ name: auth_hash['info']['name'],
+ email: auth_hash['info']['email']
+ )
+
+ end
+
+ def self.create_from_github(auth_hash)
+ user = build_from_github(auth_hash)
+ user.save
+ user
+ end
+
+end
diff --git a/app/views/categories/index.html.erb b/app/views/categories/index.html.erb
new file mode 100644
index 0000000000..89ca2f00c6
--- /dev/null
+++ b/app/views/categories/index.html.erb
@@ -0,0 +1,13 @@
+
+
+ Categories:
+ <% @categories = Category.all %>
+ <% @categories.each do |cat| %>
+
+
+
+ <% end %>
+
+
+
+
diff --git a/app/views/categories/new.html.erb b/app/views/categories/new.html.erb
new file mode 100644
index 0000000000..56302d100b
--- /dev/null
+++ b/app/views/categories/new.html.erb
@@ -0,0 +1,20 @@
+
+<% if @current_user %>
+ <%= form_with model: @category do |f|%>
+
+ Add a category
+
+
+ <%= f.label :name %>
+ <%= f.text_field :name, class: "form-control", placeholder: "Enter name of new category here" %>
+
+
+ <%= f.hidden_field :user_id, :value => @current_user.id %>
+
+ <%= f.submit "Add category", class: "btn btn-outline-primary" %>
+
+ <% end %>
+<% else %>
+ Hi You have to be signed in to add a category
+<% end %>
+
diff --git a/app/views/categories/show.html.erb b/app/views/categories/show.html.erb
new file mode 100644
index 0000000000..fef14844e5
--- /dev/null
+++ b/app/views/categories/show.html.erb
@@ -0,0 +1,46 @@
+
+ <%= @category.name.capitalize %>
+
+ <% @category.products.each do |product| %>
+ <% in_cart = product.in_cart?(session[:cart]) %>
+ <% product_stock_select = product.stock_count > 1 ? [*1..product.stock_count] : [product.stock_count] %>
+ <% cart_adjust_quantity = product.cart_adjust_quantity(session[:cart]) if in_cart %>
+ <% cart_adjust_select = cart_adjust_quantity > 1 ? [*1..cart_adjust_quantity] : [cart_adjust_quantity] if in_cart%>
+ <% option_values = in_cart ? cart_adjust_select : product_stock_select %>
+
+ <% if product.stock_count > 0 %>
+
+ <%= image_tag product.photo_url, class: "card-img-top"%>
+
+
<%= link_to product.name.capitalize, product_path(product.id) %>
+
<%= product.user.name %>
+ <% if product.reviews.length > 0 %>
+
+ <% product.average_rating.times do %>
+
+ <% end %>
+ (<%= product.reviews.length %>)
+
+ <% end %>
+
<%= money_display(product.price) %>
+ <% if option_values[0] > 0 %>
+
<%= form_with url: add_to_cart_path(product.id), method: :post do |f| %>
+ <%= f.select :quantity, options_for_select(option_values) %>
+ <%= f.submit "Add to Basket", class: "btn btn-outline-primary"%>
+ <% end %>
+
+ <% else %>
+
All the <%= product.name.capitalize.pluralize %> are in your cart!
+
<%= link_to "Remove from Basket", remove_from_cart_path(product.id), class: "btn btn-outline-primary"%>
+ <% end %>
+ <% if logged_in? %>
+ <% if current_user.id == product.user.id %>
+
<%= link_to "Update Creature", edit_product_path(product.id)%>
+ <% end %>
+ <% 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..14f1b1f968
--- /dev/null
+++ b/app/views/layouts/application.html.erb
@@ -0,0 +1,118 @@
+
+
+
+ Adoptsy
+ <%= csrf_meta_tags %>
+ <%= csp_meta_tag %>
+ <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
+ <%= favicon_link_tag(source='favicon.png', options={}) %>
+ <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
+
+
+
+
+
+
+
+
+
+ <% if flash[:messages] %>
+
+
Request count not be completed.
+
+ <% flash[:messages].each do |name, problem| %>
+ <% if !problem.empty? %>
+ - <%= name.capitalize %>:
+
+ <% problem.each do |validation| %>
+ -
+ <%= validation.capitalize %>
+
+ <% end %>
+
+ <% end %>
+
+ <% end %>
+
+
+ <% else %>
+ <% flash.each do |name, message| %>
+ <%= message %>
+ <% end %>
+ <% 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/orders/_fulfillment.html.erb b/app/views/orders/_fulfillment.html.erb
new file mode 100644
index 0000000000..896e778b23
--- /dev/null
+++ b/app/views/orders/_fulfillment.html.erb
@@ -0,0 +1,66 @@
+ <%= @current_user.name %>'s Fulfillment Page: <%= status %>
+
+
+
+
+
+
+
+
+
+
+
+
+ | Creature |
+ Cost |
+ Count |
+ Paid |
+ Adoption Request Date |
+ Adoption Request # |
+ Creature Status |
+
+
+
+ <% @orderproducts.each do |orderproduct| %>
+
+ |
+ <%= link_to orderproduct.product.name, product_path(orderproduct.product.id) %>
+ |
+
+ <%= money_display(orderproduct.product.price) %>
+ |
+
+ <%= orderproduct.quantity %>
+ |
+
+ <%= money_display(orderproduct.quantity * orderproduct.product.price) %>
+ |
+
+ <%= readable_date(orderproduct.order.created_at) %>
+ |
+
+ <%= link_to "#{orderproduct.order_id}", order_path(orderproduct.order_id) %>
+ |
+
+ <%= form_with model: orderproduct do |f| %>
+ <%= f.label :status %>
+ <%= f.select :status, options_for_select(["pending", "shipped"], :selected => orderproduct.status) %>
+ <%= f.submit "Change Status", class: "btn btn-outline-primary" %>
+ <% end %>
+ |
+
+ <% end %>
+
+
+
+
+ Adoption Requests Count: <%= @count %>
+ Fundraising Revenue for all <%= status %>: <%= money_display(@total_revenue) %>
+
diff --git a/app/views/orders/cancelled.html.erb b/app/views/orders/cancelled.html.erb
new file mode 100644
index 0000000000..eaa5cf6dbf
--- /dev/null
+++ b/app/views/orders/cancelled.html.erb
@@ -0,0 +1,3 @@
+
+ <%= render partial: "fulfillment", locals: { status: "Cancelled Adoption Requests" } %>
+
diff --git a/app/views/orders/completed.html.erb b/app/views/orders/completed.html.erb
new file mode 100644
index 0000000000..644e54965e
--- /dev/null
+++ b/app/views/orders/completed.html.erb
@@ -0,0 +1,3 @@
+
+ <%= render partial: "fulfillment", locals: { status: "Completed Adoption Requests" } %>
+
diff --git a/app/views/orders/fulfillment.html.erb b/app/views/orders/fulfillment.html.erb
new file mode 100644
index 0000000000..6ee1f201d6
--- /dev/null
+++ b/app/views/orders/fulfillment.html.erb
@@ -0,0 +1,3 @@
+
+ <%= render partial: "fulfillment", locals: { status: "Adoption Requests" }%>
+
diff --git a/app/views/orders/new.html.erb b/app/views/orders/new.html.erb
new file mode 100644
index 0000000000..9ad2cc0734
--- /dev/null
+++ b/app/views/orders/new.html.erb
@@ -0,0 +1,42 @@
+
+ <%= form_with model: @order do |f| %>
+
+
+ <%= f.label :name, "Name: " %>
+ <%= f.text_field :name, class: "form-control", placeholder: "Enter name" %>
+
+
+
+ <%= f.label :email, "Email: " %>
+ <%= f.text_field :email, class: "form-control", placeholder: "Enter email" %>
+
+
+
+ <%= f.label :mailing_address, "Mailing Address: " %>
+ <%= f.text_field :mailing_address, class: "form-control", placeholder: "Type mailing address" %>
+
+
+
+ <%= f.label :zip_code, "Zip Code: " %>
+ <%= f.text_field :zip_code, class: "form-control", placeholder: "Enter zip code here" %>
+
+
+
+ <%= f.label :cc_number, "Credit Card Number: " %>
+ <%= f.text_field :cc_number, class: "form-control", placeholder: "Credit card number" %>
+
+
+
+ <%= f.label :cc_expiration, "Credit Card Expiration: " %>
+ <%= f.text_field :cc_expiration, class: "form-control", placeholder: "Expiration date" %>
+
+
+
+ <%= f.label :cc_cvv, "Credit Card Security Code (cvv): " %>
+ <%= f.text_field :cc_cvv, class: "form-control", placeholder: "Cvv" %>
+
+
+ <%= f.submit "Submit", class: "btn btn-outline-primary" %>
+
+ <% end %>
+
diff --git a/app/views/orders/notfound.html.erb b/app/views/orders/notfound.html.erb
new file mode 100644
index 0000000000..01bfeb4a28
--- /dev/null
+++ b/app/views/orders/notfound.html.erb
@@ -0,0 +1,4 @@
+
+ 404
+ Oops! The adoption request you were looking for cannot be found.
+
diff --git a/app/views/orders/paid.html.erb b/app/views/orders/paid.html.erb
new file mode 100644
index 0000000000..bf4617d28b
--- /dev/null
+++ b/app/views/orders/paid.html.erb
@@ -0,0 +1,3 @@
+
+ <%= render partial: "fulfillment", locals: { status: "Paid Adoption Requests" } %>
+
diff --git a/app/views/orders/show.html.erb b/app/views/orders/show.html.erb
new file mode 100644
index 0000000000..e5659aa01d
--- /dev/null
+++ b/app/views/orders/show.html.erb
@@ -0,0 +1,46 @@
+
+
+Viewing Adoption Request #<%= @order.id %>
+<% if @current_user && (@current_user.orders.include? @order) %>
+
+ Adoption Parent: <%= @order.name %>
+ Adoption Parent Email: <%= @order.email %>
+ Delivery Street Address: <%= @order.mailing_address %>
+ Delivery Zip Code: <%= @order.zip_code %>
+ Credit Card (Last Four Digits): <%= @order.cc_number.to_s.split("").last(4).join %>
+ Credit Card Expiration: <%= @order.cc_expiration %>
+ Adoption Request Date: <%= readable_date(@order.created_at) %>
+
+<% else %>
+
+ Adoption Parent: <%= @order.name %>
+ Delivery Address: <%= @order.mailing_address %>
+ Adoption Total: <%= money_display(@order.total_cost) %>
+ Adoption Status: <%= @order.status %>
+ Adoption Request Date: <%= readable_date(@order.created_at) %>
+
+<% end %>
+
+
+
+
+
+ | Creature Name |
+ Adoption Price |
+ Number of Creatures |
+ Total |
+
+
+
+ <% @order.orderproducts.each do |orderproduct| %>
+
+ <%= link_to orderproduct.product.name, product_path(orderproduct.product.id) %> |
+ <%= money_display(orderproduct.product.price) %> |
+ <%= orderproduct.quantity %> |
+ <%= money_display(orderproduct.product.price * orderproduct.quantity) %> |
+
+ <% end %>
+
+
+ Please contact customer support to cancel this adoption.
+
diff --git a/app/views/products/_form.html.erb b/app/views/products/_form.html.erb
new file mode 100644
index 0000000000..195e318aff
--- /dev/null
+++ b/app/views/products/_form.html.erb
@@ -0,0 +1,47 @@
+
+
+ <%= form_with model: @product do |f|%>
+
+ <%= page_title %>
+
+
+ <%= f.label :name %>
+ <%= f.text_field :name, class: "form-control", placeholder: "Enter name"%>
+
+
+
+ <%= f.label :available_creatures_count %>
+ <%= f.text_field :stock_count, class: "form-control", placeholder: "Enter number of available creatures" %>
+
+
+
+ <%= f.label :price %>
+ <%= f.text_field :price, class: "form-control", placeholder: "Enter price" %>
+
+
+
+ <%= f.label :description %>
+ <%= f.text_area :description, class: "form-control", placeholder: "Add a description" %>
+
+
+
+ <%= f.label :photo_url %>
+ <%= f.text_field :photo_url, class: "form-control", placeholder: "Photo url here" %>
+
+
+
+ <%= f.label :categories, class: "form-check-label" %>
+ <%= collection_check_boxes(:product, :category_ids, Category.all, :id, :name) do |b| %>
+ <%= b.label(class: "check_box") do %>
+ <%= b.check_box(class: "check_box") %>
+ <%= b.object.name %>
+ <% end %>
+ <% end %>
+
+
+
+ <%= f.hidden_field :user_id, :value => @current_user.id %>
+
+ <%= f.submit "Save Creature", class: "btn btn-outline-primary" %>
+
+ <% end %>
diff --git a/app/views/products/edit.html.erb b/app/views/products/edit.html.erb
new file mode 100644
index 0000000000..1d7f0ed1ab
--- /dev/null
+++ b/app/views/products/edit.html.erb
@@ -0,0 +1 @@
+<%= render partial: "form", locals: { page_title: "Edit the creature in your inventory:"} %>
diff --git a/app/views/products/home.html.erb b/app/views/products/home.html.erb
new file mode 100644
index 0000000000..ade38d075f
--- /dev/null
+++ b/app/views/products/home.html.erb
@@ -0,0 +1,3 @@
+
+ <%= image_tag("bird.jpg") %>
+
diff --git a/app/views/products/index.html.erb b/app/views/products/index.html.erb
new file mode 100644
index 0000000000..3e38e6d601
--- /dev/null
+++ b/app/views/products/index.html.erb
@@ -0,0 +1,50 @@
+
+ Creatures
+
+ <% if @products %>
+
+ <% @products.each do |product| %>
+ <% in_cart = product.in_cart?(session[:cart]) %>
+ <% product_stock_select = product.stock_count > 1 ? [*1..product.stock_count] : [product.stock_count] %>
+ <% cart_adjust_quantity = product.cart_adjust_quantity(session[:cart]) if in_cart %>
+ <% cart_adjust_select = cart_adjust_quantity > 1 ? [*1..cart_adjust_quantity] : [cart_adjust_quantity] if in_cart%>
+ <% option_values = in_cart ? cart_adjust_select : product_stock_select %>
+
+ <% if product.stock_count > 0 %>
+
+ <%= image_tag product.photo_url, class: "card-img-top"%>
+
+
<%= link_to product.name.capitalize, product_path(product.id) %>
+
<%= product.user.name %>
+ <% if product.reviews.length > 0 %>
+
+ <% product.average_rating.times do %>
+
+ <% end %>
+ (<%= product.reviews.length %>)
+
+ <% end %>
+
<%= money_display(product.price) %>
+ <% if option_values[0] > 0 %>
+
<%= form_with url: add_to_cart_path(product.id), method: :post do |f| %>
+ <%= f.select :quantity, options_for_select(option_values) %>
+ <%= f.submit "Add to Basket", class: "btn btn-outline-primary"%>
+ <% end %>
+
+ <% elsif option_values[0] <= 0 && in_cart %>
+
All the <%= product.name.capitalize.pluralize %> are in your basket!
+
<%= link_to "Remove from Basket", remove_from_cart_path(product.id), class: "btn btn-outline-primary"%>
+ <% end %>
+ <% if logged_in? %>
+ <% if current_user.id == product.user.id %>
+
<%= link_to "Update Creature", edit_product_path(product.id)%>
+ <% end %>
+ <% end %>
+
+
+
+ <% end %>
+ <% end %>
+
+ <% end %>
+
diff --git a/app/views/products/new.html.erb b/app/views/products/new.html.erb
new file mode 100644
index 0000000000..81f4a53dfb
--- /dev/null
+++ b/app/views/products/new.html.erb
@@ -0,0 +1,3 @@
+
diff --git a/app/views/products/notfound.html.erb b/app/views/products/notfound.html.erb
new file mode 100644
index 0000000000..6fa5fff705
--- /dev/null
+++ b/app/views/products/notfound.html.erb
@@ -0,0 +1,4 @@
+
+ 404
+ Oops! The creature you were looking for cannot be found. It must have found a home.
+
diff --git a/app/views/products/show.html.erb b/app/views/products/show.html.erb
new file mode 100644
index 0000000000..90f7d2a9d7
--- /dev/null
+++ b/app/views/products/show.html.erb
@@ -0,0 +1,110 @@
+
+<% in_cart = @product.in_cart?(session[:cart]) %>
+<% product_stock_select = @product.stock_count > 1 ? [*1..@product.stock_count] : [@product.stock_count] %>
+<% cart_adjust_quantity = @product.cart_adjust_quantity(session[:cart]) if in_cart %>
+<% cart_adjust_select = cart_adjust_quantity > 1 ? [*1..cart_adjust_quantity] : [cart_adjust_quantity] if in_cart%>
+<% option_values = in_cart ? cart_adjust_select : product_stock_select %>
+
+
+ <%= image_tag @product.photo_url %>
+
+ <%= @product.name.capitalize %>
+ <%= link_to @product.user.name, user_path(@product.user.id)%>
+ Price: <%= money_display(@product.price) %>
+ <% if option_values[0] > 0 %>
+ <%= form_with url: add_to_cart_path(@product.id), method: :post do |f| %>
+ <%= f.select :quantity, options_for_select(option_values) %>
+ <%= f.submit "Add to Cart", class: "btn btn-outline-primary"%>
+ <% end %>
+
+ <% elsif option_values[0] <= 0 && in_cart %>
+ All the <%= @product.name.capitalize.pluralize %> are in your cart!
+ <%= link_to "Remove from Cart", remove_from_cart_path(@product.id), class: "btn btn-outline-primary"%>
+ <% elsif option_values[0] == 0 && !in_cart %>
+ Creature unavailable!
+ <% end %>
+ Description
+ <%= @product.description %>
+ <% if logged_in? && @current_user.id == @product.user.id %>
+ <%= link_to "Update Creature", edit_product_path(@product.id), class: "btn btn-outline-primary"%>
+ <% end %>
+
+
+
+ <% if (@product.reviews.length > 0) && (@product.user.products.length > 1) %>
+
+
+ Creature reviews:
+ <% @product.reviews.each do |review| %>
+
+ -
+
Reviewer: <%= review.name %>
+ Rating: <%= review.rating %>
+ Review: <%= review.review %>
+
+
+ <% end %>
+ <% if @product.user != @current_user %>
+ <%= link_to "Leave a Review", new_product_review_path(@product.id), class: "btn btn-outline-primary" %>
+ <% end %>
+
+
+
+ More from this rescuer
+
+ <% @product.user.products.first(4).each do |product| %>
+ <% if product.id != @product.id %>
+ <%= link_to image_tag(product.photo_url), product_path(product.id), class: "user-products-img" %>
+ <%= link_to product.name.capitalize, product_path(product.id)%>
+ <%= money_display(@product.price) %>
+ <% end %>
+ <% end %>
+
+
+
+ <% elsif (@product.reviews.length > 0) && (@product.user.products.length == 1) %>
+
+
+ Creature reviews:
+ <% @product.reviews.each do |review| %>
+
+ -
+
Reviewer: <%= review.name %>
+ Rating: <%= review.rating %>
+ Review: <%= review.review %>
+
+
+ <% end %>
+ <% if @product.user != @current_user %>
+ <%= link_to "Leave a Review", new_product_review_path(@product.id), class: "btn btn-outline-primary" %>
+ <% end %>
+
+
+ <% elsif (@product.reviews.length == 0) && (@product.user.products.length > 1) %>
+
+
+ <% if @product.user != @current_user %>
+ <%= link_to "Leave a Review", new_product_review_path(@product.id), class: "btn btn-outline-primary" %>
+ <% end %>
+ More from this rescuer
+
+ <% @product.user.products.first(4).each do |product| %>
+ <% if product.id != @product.id %>
+ <%= link_to image_tag(product.photo_url), product_path(product.id), class: "user-products-img" %>
+ <%= link_to product.name.capitalize, product_path(product.id)%>
+ <%= money_display(@product.price) %>
+ <% end %>
+ <% end %>
+
+
+ <% else %>
+
+
+ <% if @product.user != @current_user %>
+ <%= link_to "Leave a Review", new_product_review_path(@product.id), class: "btn btn-outline-primary" %>
+ <% end %>
+
+ <% end %>
+
+
+
diff --git a/app/views/reviews/new.html.erb b/app/views/reviews/new.html.erb
new file mode 100644
index 0000000000..7c1fd14774
--- /dev/null
+++ b/app/views/reviews/new.html.erb
@@ -0,0 +1,25 @@
+
+Leave a Review
+
+<%= form_with(model: @review, url: [@product, @review]) do |f| %>
+
+
+ <%= f.label :name %>
+ <%= f.text_field :name, class: "form-control", placeholder: "Enter name" %>
+
+
+
+ <%= f.label :rating %>
+ <%= f.select :rating, options_for_select([1, 2, 3, 4, 5]), class: "form form-group form-control" %>
+
+
+
+ <%= f.label :review %>
+ <%= f.text_area :review, class: "form-control", placeholder: "Give a review" %>
+
+
+<%= f.hidden_field :product_id, :value => @product.id %>
+
+<%= f.submit "Submit Review", class: "btn btn-outline-primary" %>
+<% end %>
+
diff --git a/app/views/sessions/cart.html.erb b/app/views/sessions/cart.html.erb
new file mode 100644
index 0000000000..84f165817f
--- /dev/null
+++ b/app/views/sessions/cart.html.erb
@@ -0,0 +1,49 @@
+
+Adoption Basket
+<% total = 0 %>
+
+
+
+ |
+ Name |
+ Price |
+ Total |
+ Quantity |
+ Remove from Basket |
+
+
+
+ <% @cart_items.each do |cart_array| %>
+ <%
+ product = cart_array[0]
+ quantity = cart_array[1]
+ option_values = product.stock_count > 1 ? [*1..product.stock_count] : [product.stock_count]
+ %>
+
+ | <%= image_tag product.photo_url, class: "cart-img"%> |
+ <%= link_to product.name.capitalize, product_path(product.id) %> |
+ <%= money_display(product.price)%> |
+ <% total += (quantity * product.price) %>
+ <%= money_display(quantity * product.price) %> |
+
+ <%= form_with url: update_cart_path(product.id), method: :patch do |f| %>
+ <%= f.select :quantity, options_for_select(option_values, :selected => quantity) %>
+ <%= f.submit "Update", class: "btn btn-outline-primary"%>
+ <% end %>
+ |
+ <%= link_to "Remove from Basket", remove_from_cart_path(product.id), class: "btn btn-outline-primary"%> |
+
+ <% end %>
+
+
+<% if total > 0 %>
+
+ Basket Total: <%= money_display(total) %>
+ <%= link_to "Checkout Basket", new_order_path, class: "btn btn-outline-primary"%>
+
+<% else %>
+
+<% end %>
+
diff --git a/app/views/sessions/new.html.erb b/app/views/sessions/new.html.erb
new file mode 100644
index 0000000000..d94897a09d
--- /dev/null
+++ b/app/views/sessions/new.html.erb
@@ -0,0 +1,13 @@
+Log In
+
+<%= form_with model: @user, class: "user-form", url: login_path, method: :post do |f|%>
+ Please enter login details:
+
+
+ <%= f.label :name %>
+ <%= f.text_field :name, class: "form-control", placeholder: "Enter name please" %>
+
+
+ <%= f.submit "Log In", class: "btn-outline-primary" %>
+
+<% end %>
diff --git a/app/views/sessions/notfound.html.erb b/app/views/sessions/notfound.html.erb
new file mode 100644
index 0000000000..6fa5fff705
--- /dev/null
+++ b/app/views/sessions/notfound.html.erb
@@ -0,0 +1,4 @@
+
+ 404
+ Oops! The creature you were looking for cannot be found. It must have found a home.
+
diff --git a/app/views/users/index.html.erb b/app/views/users/index.html.erb
new file mode 100644
index 0000000000..4d76ff163a
--- /dev/null
+++ b/app/views/users/index.html.erb
@@ -0,0 +1,6 @@
+Creature Rescuers
+
+<% @users.each do |user| %>
+ <%= link_to user.name, user_path(user) %>
+
+<% end %>
diff --git a/app/views/users/notfound.html.erb b/app/views/users/notfound.html.erb
new file mode 100644
index 0000000000..c37ab2ebaa
--- /dev/null
+++ b/app/views/users/notfound.html.erb
@@ -0,0 +1 @@
+Not Found 404
diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb
new file mode 100644
index 0000000000..8a63009aea
--- /dev/null
+++ b/app/views/users/show.html.erb
@@ -0,0 +1,50 @@
+
+ Creature Rescuer
+ <%= @user.name %>
+
+
+ <% @user.products.each do |product| %>
+ <% in_cart = product.in_cart?(session[:cart]) %>
+ <% product_stock_select = product.stock_count > 1 ? [*1..product.stock_count] : [product.stock_count] %>
+ <% cart_adjust_quantity = product.cart_adjust_quantity(session[:cart]) if in_cart %>
+ <% cart_adjust_select = cart_adjust_quantity > 1 ? [*1..cart_adjust_quantity] : [cart_adjust_quantity] if in_cart%>
+ <% option_values = in_cart ? cart_adjust_select : product_stock_select %>
+
+ <% if product.stock_count > 0 || @current_user == product.user %>
+
+ <%= image_tag product.photo_url, class: "card-img-top"%>
+
+
<%= link_to product.name.capitalize, product_path(product.id) %>
+
<%= product.user.name %>
+ <% if product.reviews.length > 0 %>
+
+ <% product.average_rating.times do %>
+
+ <% end %>
+ (<%= product.reviews.length %>)
+
+ <% end %>
+
<%= money_display(product.price) %>
+ <% if option_values[0] > 0 %>
+
<%= form_with url: add_to_cart_path(product.id), method: :post do |f| %>
+ <%= f.select :quantity, options_for_select(option_values) %>
+ <%= f.submit "Add to Basket", class: "btn btn-outline-primary"%>
+ <% end %>
+
+ <% elsif (current_user == product.user) && !in_cart && (option_values[0] <= 0) %>
+
<%= product.name.capitalize %> is unavailable. Update creature to make available.
+ <% elsif option_values[0] <= 0 && in_cart %>
+
All the <%= product.name.capitalize.pluralize %> are in your basket!
+
<%= link_to "Remove from Basket", remove_from_cart_path(product.id), class: "btn btn-outline-primary"%>
+ <% end %>
+ <% if logged_in? %>
+ <% if current_user.id == product.user.id %>
+
<%= link_to "Update Creature", edit_product_path(product.id), class: "btn btn-outline-primary"%>
+ <% end %>
+ <% end %>
+
+
+ <% end %>
+ <% end %>
+
+
diff --git a/bin/bundle b/bin/bundle
new file mode 100755
index 0000000000..f19acf5b5c
--- /dev/null
+++ b/bin/bundle
@@ -0,0 +1,3 @@
+#!/usr/bin/env ruby
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
+load Gem.bin_path('bundler', 'bundle')
diff --git a/bin/rails b/bin/rails
new file mode 100755
index 0000000000..fd0e86d643
--- /dev/null
+++ b/bin/rails
@@ -0,0 +1,15 @@
+#!/usr/bin/env ruby
+if ENV['RAILS_ENV'] == 'test'
+ require 'simplecov'
+ SimpleCov.start 'rails'
+ puts "required simplecov"
+end
+
+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..94fd4d7977
--- /dev/null
+++ b/bin/setup
@@ -0,0 +1,36 @@
+#!/usr/bin/env ruby
+require 'fileutils'
+include FileUtils
+
+# path to your application root.
+APP_ROOT = File.expand_path('..', __dir__)
+
+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..58bfaed518
--- /dev/null
+++ b/bin/update
@@ -0,0 +1,31 @@
+#!/usr/bin/env ruby
+require 'fileutils'
+include FileUtils
+
+# path to your application root.
+APP_ROOT = File.expand_path('..', __dir__)
+
+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')
+
+ # Install JavaScript dependencies if using Yarn
+ # system('bin/yarn')
+
+ 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..460dd565b4
--- /dev/null
+++ b/bin/yarn
@@ -0,0 +1,11 @@
+#!/usr/bin/env ruby
+APP_ROOT = File.expand_path('..', __dir__)
+Dir.chdir(APP_ROOT) do
+ begin
+ exec "yarnpkg", *ARGV
+ 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..5c09a3eefc
--- /dev/null
+++ b/config/application.rb
@@ -0,0 +1,25 @@
+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.2
+
+ # Settings in config/environments/* take precedence over those specified here.
+ # Application configuration can go into files in config/initializers
+ # -- all .rb files in that directory are automatically loaded after loading
+ # the framework and any gems in your application.
+ end
+end
diff --git a/config/boot.rb b/config/boot.rb
new file mode 100644
index 0000000000..b9e460cef3
--- /dev/null
+++ b/config/boot.rb
@@ -0,0 +1,4 @@
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
+
+require 'bundler/setup' # Set up gems listed in the Gemfile.
+require 'bootsnap/setup' # Speed up boot time by caching expensive operations.
diff --git a/config/cable.yml b/config/cable.yml
new file mode 100644
index 0000000000..dd2a324c68
--- /dev/null
+++ b/config/cable.yml
@@ -0,0 +1,10 @@
+development:
+ adapter: async
+
+test:
+ adapter: async
+
+production:
+ adapter: redis
+ url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
+ channel_prefix: betsy_production
diff --git a/config/credentials.yml.enc b/config/credentials.yml.enc
new file mode 100644
index 0000000000..81054d63bb
--- /dev/null
+++ b/config/credentials.yml.enc
@@ -0,0 +1 @@
+zp/2At8o7D7zjNjZECssyNuJCFGe2yW5X+jNQ60fENNZ3bcpltND+2CPFxxXJkef+NPXgbVWDgnBMIY0gY+6c6+3Qvsj01iYThtAAsI23JlavNRYzCVbCuebwHVdHS6j2AvKlZZslNQKx+V7tvQm1YwVEo9OBUT2zOhzto+lhhX4ZmOVk4DbIzk8LBbF3ZbZFtDQaP14DUohInTuy4p+7HQ3iDBr3VrwozVzW1gOkTXO7URzEBQVCfuqW1Frh1MU+Qw3imWcJ18kUFyjTULaoQu95Gzdto1BSnyo5GjWqADm1zxQGh6Lw8F1Avg4jO0rukn1t6VECML3ngZaPjEgxpTIujvkBFe9Pkv/GilCErzswnDAuoJSryh4CU6ZZOKtqvMpy0f11W3Y1WMYxJ3S4qd5SA5RqLbVJP+3--CqQA02MptuQmrkjE--KjCmy4Q38OB0rSBzHLNq3w==
\ No newline at end of file
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..1311e3e4ef
--- /dev/null
+++ b/config/environments/development.rb
@@ -0,0 +1,61 @@
+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.
+ # Run rails dev:cache to toggle caching.
+ 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.to_i}"
+ }
+ else
+ config.action_controller.perform_caching = false
+
+ config.cache_store = :null_store
+ end
+
+ # Store uploaded files on the local file system (see config/storage.yml for options)
+ config.active_storage.service = :local
+
+ # 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
+
+ # Highlight code that triggered database queries in logs.
+ config.active_record.verbose_query_logs = true
+
+ # 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..5f6f3058c6
--- /dev/null
+++ b/config/environments/production.rb
@@ -0,0 +1,94 @@
+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
+
+ # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
+ # or in config/master.key. This key is used to decrypt credentials (and other encrypted files).
+ # config.require_master_key = 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
+
+ # Store uploaded files on the local file system (see config/storage.yml for options)
+ config.active_storage.service = :local
+
+ # 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..0a38fd3ce9
--- /dev/null
+++ b/config/environments/test.rb
@@ -0,0 +1,46 @@
+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.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
+
+ # Store uploaded files on the local file system in a temporary directory
+ config.active_storage.service = :test
+
+ 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/action_view.rb b/config/initializers/action_view.rb
new file mode 100644
index 0000000000..142d382f87
--- /dev/null
+++ b/config/initializers/action_view.rb
@@ -0,0 +1 @@
+Rails.application.config.action_view.form_with_generates_remote_forms = false
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/content_security_policy.rb b/config/initializers/content_security_policy.rb
new file mode 100644
index 0000000000..d3bcaa5ec8
--- /dev/null
+++ b/config/initializers/content_security_policy.rb
@@ -0,0 +1,25 @@
+# Be sure to restart your server when you modify this file.
+
+# Define an application-wide content security policy
+# For further information see the following documentation
+# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
+
+# Rails.application.config.content_security_policy do |policy|
+# policy.default_src :self, :https
+# policy.font_src :self, :https, :data
+# policy.img_src :self, :https, :data
+# policy.object_src :none
+# policy.script_src :self, :https
+# policy.style_src :self, :https
+
+# # Specify URI for violation reports
+# # policy.report_uri "/csp-violation-report-endpoint"
+# end
+
+# If you are using UJS then enable automatic nonce generation
+# Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
+
+# Report CSP violations to a specified URI
+# For further information see the following documentation:
+# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
+# Rails.application.config.content_security_policy_report_only = true
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/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..a5eccf816b
--- /dev/null
+++ b/config/puma.rb
@@ -0,0 +1,34 @@
+# 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.
+#
+# preload_app!
+
+# 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..12404596ce
--- /dev/null
+++ b/config/routes.rb
@@ -0,0 +1,33 @@
+Rails.application.routes.draw do
+ root "products#home"
+
+ get "/products/cart", to: "sessions#cart_view", as: "cart"
+
+ get "/auth/:provider/callback", to: "sessions#create", as: "login"
+ delete 'sessions/destroy', to: 'sessions#destroy', as: 'logout'
+
+ get "/orders/search", to: "orders#search", as: 'search_orders'
+ resources :orders, except: [:delete, :edit, :index]
+
+ resources :products do
+ resources :reviews, only: [:new, :create]
+ end
+
+ post "/products/:id/add_to_cart", to: "sessions#add_to_cart", as: "add_to_cart"
+ patch "/products/:id/update_cart", to: "sessions#update_quantity", as: "update_cart"
+ get "/products/:id/remove", to: "sessions#remove_from_cart", as: "remove_from_cart"
+
+ resources :users, except: [:edit, :delete]
+
+ resources :categories
+
+ resources :orderproducts, only: [:update]
+
+ get "/fulfillment", to: "orders#fulfillment", as: "get_orders"
+ get "/fulfillment/paid", to: "orders#paid", as: "paid_orders"
+ get "/fulfillment/pending", to: "orders#completed", as: "completed_orders"
+ get "/fulfillment/cancelled", to: "orders#cancelled", as: "cancelled_orders"
+
+ # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
+
+end
diff --git a/config/spring.rb b/config/spring.rb
new file mode 100644
index 0000000000..9fa7863f99
--- /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/config/storage.yml b/config/storage.yml
new file mode 100644
index 0000000000..d32f76e8fb
--- /dev/null
+++ b/config/storage.yml
@@ -0,0 +1,34 @@
+test:
+ service: Disk
+ root: <%= Rails.root.join("tmp/storage") %>
+
+local:
+ service: Disk
+ root: <%= Rails.root.join("storage") %>
+
+# Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
+# amazon:
+# service: S3
+# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
+# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
+# region: us-east-1
+# bucket: your_own_bucket
+
+# Remember not to checkin your GCS keyfile to a repository
+# google:
+# service: GCS
+# project: your_project
+# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %>
+# bucket: your_own_bucket
+
+# Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
+# microsoft:
+# service: AzureStorage
+# storage_account_name: your_account_name
+# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>
+# container: your_container_name
+
+# mirror:
+# service: Mirror
+# primary: local
+# mirrors: [ amazon, google, microsoft ]
diff --git a/db/category_seeds.csv b/db/category_seeds.csv
new file mode 100644
index 0000000000..92bbdf75d1
--- /dev/null
+++ b/db/category_seeds.csv
@@ -0,0 +1,7 @@
+name
+mammals
+amphibians
+reptiles
+birds
+sea creatures
+mystical
diff --git a/db/creature_seeds.csv b/db/creature_seeds.csv
new file mode 100644
index 0000000000..2de0533c70
--- /dev/null
+++ b/db/creature_seeds.csv
@@ -0,0 +1,27 @@
+name,stock_count,description,price,category,photo_url
+Frog,4,Your favorite cuddley non-posionous amphibian,2000,amphibians,https://i.imgur.com/JvI9dY9.jpg
+Puppy,12,A baby dalmatian!,1500,mammals,https://i.imgur.com/gdxm25d.jpg
+Hippo,5,Cute and dangerous!,100000,mammals,https://i.imgur.com/YDCtFMB.jpg
+Sloth,4,Adorable!,20000,mammals,https://i.imgur.com/Ht1hezp.jpg
+Pomeranian,24,Fluffy like a huge cloud!,2000,mammals,https://i.imgur.com/Om3wQCW.jpg
+Python,8,Baby ball python with pretty reticulated pattern,200,reptiles,https://i.imgur.com/IbbN3gc.jpg
+Elephant,3,Cute and friendly baby elephant,3000,mammals,https://i.imgur.com/DZ2o9Um.jpg
+Labrador,12,Playful and adorable - your new best friend,200,mammals,https://i.imgur.com/YXg8LXR.jpg
+Piglet,10,Lovely pink and friendly,100,mammals,https://i.imgur.com/MpKih6T.jpg
+Parrot,9,Gorgeous colors - talk like human beings,4000,birds,https://i.imgur.com/YDknvAm.jpg
+Meerkat,12,Fun to watch and lively - shrewd little animals,3000,mammals,https://i.imgur.com/Kc3P7k3.jpg
+Lamb,14,Soft and gentle,200,mammals,https://i.imgur.com/gA9xPLp.jpg
+Leopard,4,15 day old leopard cubs,5000,mammals,https://i.imgur.com/9POrqMS.jpg
+Gibbon,8,Smart as a whistle,2000,mammals,https://i.imgur.com/0tVvi5N.jpg
+Fox,12,Cubs-playful and fun to watch,2500,mammals,https://i.imgur.com/QBF7Gm4.jpg
+Giraffe,4,New born baby giraffe,4000,mammals,https://i.imgur.com/4WkiWsX.jpg
+Fawn,9,Timid little baby fawn,1000,mammals,https://i.imgur.com/5NVi4vr.jpg
+Turtle,40,Tine little freshwater turtle,500,reptiles,https://i.imgur.com/I2ICTfg.jpg
+Red-panda,8,Beautiful red fur and bushy tail,3000,mammalss,https://i.imgur.com/abDkafw.jpg
+Owl,11,Clever little baby owl,2000,birds,https://i.imgur.com/v1oRd62.jpg
+Orangutan,6,Fun to watch antics - very sociable,4000,mammals,https://i.imgur.com/d66wSjt.jpg
+Squirrel,18,Baby squirrel will eat nuts out of your hand,400,mammals,https://i.imgur.com/La3e2BD.jpg
+Duckling,9,Lovely bright sunshine yellow,100,birds,https://i.imgur.com/fyZBhtU.jpg
+Dormouse,24,Inquisitive baby dormouse,200,mammals,https://i.imgur.com/NyKcY9y.jpg
+Hedgehog,30,Cute little baby hedgehog,300,mammals,https://i.imgur.com/MNSTA7a.jpg
+Unicorn,1,Ultra-rare!!!!,8000,mystical,https://i.imgur.com/eI9VU0v.jpg
diff --git a/db/migrate/20181017211006_create_orders.rb b/db/migrate/20181017211006_create_orders.rb
new file mode 100644
index 0000000000..878ac6ec14
--- /dev/null
+++ b/db/migrate/20181017211006_create_orders.rb
@@ -0,0 +1,17 @@
+class CreateOrders < ActiveRecord::Migration[5.2]
+ def change
+ create_table :orders do |t|
+ t.string :name
+ t.string :email
+ t.string :mailing_address
+ t.integer :zip_code
+ t.integer :cc_number
+ t.integer :cc_expiration
+ t.integer :cc_cvv
+ t.string :status
+ t.integer :total_cost
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20181017211109_create_users.rb b/db/migrate/20181017211109_create_users.rb
new file mode 100644
index 0000000000..5fd691b5d3
--- /dev/null
+++ b/db/migrate/20181017211109_create_users.rb
@@ -0,0 +1,10 @@
+class CreateUsers < ActiveRecord::Migration[5.2]
+ def change
+ create_table :users do |t|
+ t.string :name
+ t.string :email
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20181017211147_create_products.rb b/db/migrate/20181017211147_create_products.rb
new file mode 100644
index 0000000000..afb64f7619
--- /dev/null
+++ b/db/migrate/20181017211147_create_products.rb
@@ -0,0 +1,9 @@
+class CreateProducts < ActiveRecord::Migration[5.2]
+ def change
+ create_table :products do |t|
+ t.belongs_to :user, index: true
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20181017213714_create_orderproducts.rb b/db/migrate/20181017213714_create_orderproducts.rb
new file mode 100644
index 0000000000..b6ac99fe13
--- /dev/null
+++ b/db/migrate/20181017213714_create_orderproducts.rb
@@ -0,0 +1,11 @@
+class CreateOrderproducts < ActiveRecord::Migration[5.2]
+ def change
+ create_table :orderproducts 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/20181017214200_create_reviews.rb b/db/migrate/20181017214200_create_reviews.rb
new file mode 100644
index 0000000000..fe9ea1d5c8
--- /dev/null
+++ b/db/migrate/20181017214200_create_reviews.rb
@@ -0,0 +1,12 @@
+class CreateReviews < ActiveRecord::Migration[5.2]
+ def change
+ create_table :reviews do |t|
+ t.string :name
+ t.integer :rating
+ t.string :review
+ t.belongs_to :product, index: true
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20181017220145_add_columns_to_products.rb b/db/migrate/20181017220145_add_columns_to_products.rb
new file mode 100644
index 0000000000..862c745a8b
--- /dev/null
+++ b/db/migrate/20181017220145_add_columns_to_products.rb
@@ -0,0 +1,5 @@
+class AddColumnsToProducts < ActiveRecord::Migration[5.2]
+ def change
+ add_column(:products, :stock_count, :integer)
+ end
+end
diff --git a/db/migrate/20181017220527_add_more_columns_products.rb b/db/migrate/20181017220527_add_more_columns_products.rb
new file mode 100644
index 0000000000..95d67d19bb
--- /dev/null
+++ b/db/migrate/20181017220527_add_more_columns_products.rb
@@ -0,0 +1,9 @@
+class AddMoreColumnsProducts < ActiveRecord::Migration[5.2]
+ def change
+ add_column(:products, :price, :integer)
+ add_column(:products, :category, :string)
+ add_column(:products, :photo_url, :string)
+ add_column(:products, :description, :string)
+ add_column(:products, :name, :string)
+ end
+end
diff --git a/db/migrate/20181018202045_create_categories.rb b/db/migrate/20181018202045_create_categories.rb
new file mode 100644
index 0000000000..6ccc3914a0
--- /dev/null
+++ b/db/migrate/20181018202045_create_categories.rb
@@ -0,0 +1,9 @@
+class CreateCategories < ActiveRecord::Migration[5.2]
+ def change
+ create_table :categories do |t|
+ t.string :name
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20181018202318_add_products_category_id.rb b/db/migrate/20181018202318_add_products_category_id.rb
new file mode 100644
index 0000000000..fcd0bbf112
--- /dev/null
+++ b/db/migrate/20181018202318_add_products_category_id.rb
@@ -0,0 +1,5 @@
+class AddProductsCategoryId < ActiveRecord::Migration[5.2]
+ def change
+ add_column :products, :category_id, :integer
+ end
+end
diff --git a/db/migrate/20181018202554_add_relationship_to_product.rb b/db/migrate/20181018202554_add_relationship_to_product.rb
new file mode 100644
index 0000000000..05d8e09ca9
--- /dev/null
+++ b/db/migrate/20181018202554_add_relationship_to_product.rb
@@ -0,0 +1,5 @@
+class AddRelationshipToProduct < ActiveRecord::Migration[5.2]
+ def change
+ add_index :products, :category_id
+ end
+end
diff --git a/db/migrate/20181018230310_add_userid_to_user.rb b/db/migrate/20181018230310_add_userid_to_user.rb
new file mode 100644
index 0000000000..05944ee5b1
--- /dev/null
+++ b/db/migrate/20181018230310_add_userid_to_user.rb
@@ -0,0 +1,6 @@
+class AddUseridToUser < ActiveRecord::Migration[5.2]
+ def change
+ add_column :users, :uid, :integer, :null => false
+ add_column :users, :provider, :string, :null => false
+ end
+end
diff --git a/db/migrate/20181021000611_change_int_limit.rb b/db/migrate/20181021000611_change_int_limit.rb
new file mode 100644
index 0000000000..c55417492f
--- /dev/null
+++ b/db/migrate/20181021000611_change_int_limit.rb
@@ -0,0 +1,5 @@
+class ChangeIntLimit < ActiveRecord::Migration[5.2]
+ def change
+ change_column :orders, :cc_number, :integer, limit: 8
+ end
+end
diff --git a/db/migrate/20181021201915_change_order_cardexpiration_column.rb b/db/migrate/20181021201915_change_order_cardexpiration_column.rb
new file mode 100644
index 0000000000..2bcf544e2f
--- /dev/null
+++ b/db/migrate/20181021201915_change_order_cardexpiration_column.rb
@@ -0,0 +1,5 @@
+class ChangeOrderCardexpirationColumn < ActiveRecord::Migration[5.2]
+ def change
+ change_column :orders, :cc_expiration, :string
+ end
+end
diff --git a/db/migrate/20181022180437_remove_category_column.rb b/db/migrate/20181022180437_remove_category_column.rb
new file mode 100644
index 0000000000..46b4318218
--- /dev/null
+++ b/db/migrate/20181022180437_remove_category_column.rb
@@ -0,0 +1,5 @@
+class RemoveCategoryColumn < ActiveRecord::Migration[5.2]
+ def change
+ remove_column :products, :category
+ end
+end
diff --git a/db/migrate/20181023174733_add_status_to_orderproduct.rb b/db/migrate/20181023174733_add_status_to_orderproduct.rb
new file mode 100644
index 0000000000..525e0868c6
--- /dev/null
+++ b/db/migrate/20181023174733_add_status_to_orderproduct.rb
@@ -0,0 +1,5 @@
+class AddStatusToOrderproduct < ActiveRecord::Migration[5.2]
+ def change
+ add_column :orderproducts, :status, :string
+ end
+end
diff --git a/db/migrate/20181023225325_change_money_related_columns_data_types.rb b/db/migrate/20181023225325_change_money_related_columns_data_types.rb
new file mode 100644
index 0000000000..ca43e57516
--- /dev/null
+++ b/db/migrate/20181023225325_change_money_related_columns_data_types.rb
@@ -0,0 +1,6 @@
+class ChangeMoneyRelatedColumnsDataTypes < ActiveRecord::Migration[5.2]
+ def change
+ change_column :products, :price, :decimal, :precision => 8, :scale => 2
+ change_column :orders, :total_cost, :decimal, :precision => 8, :scale => 2
+ end
+end
diff --git a/db/migrate/20181025210616_add_categoryproduct_jointable.rb b/db/migrate/20181025210616_add_categoryproduct_jointable.rb
new file mode 100644
index 0000000000..155f677ce7
--- /dev/null
+++ b/db/migrate/20181025210616_add_categoryproduct_jointable.rb
@@ -0,0 +1,10 @@
+class AddCategoryproductJointable < ActiveRecord::Migration[5.2]
+ def change
+ create_table :products_categories do |t|
+ t.belongs_to :product, index: true
+ t.belongs_to :category, index: true
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20181025212514_remove_old_categoryproduct_relationships.rb b/db/migrate/20181025212514_remove_old_categoryproduct_relationships.rb
new file mode 100644
index 0000000000..49948c09ae
--- /dev/null
+++ b/db/migrate/20181025212514_remove_old_categoryproduct_relationships.rb
@@ -0,0 +1,5 @@
+class RemoveOldCategoryproductRelationships < ActiveRecord::Migration[5.2]
+ def change
+ remove_index "category_id", name: "index_products_on_category_id"
+ end
+end
diff --git a/db/migrate/20181025215046_rename_join_table.rb b/db/migrate/20181025215046_rename_join_table.rb
new file mode 100644
index 0000000000..79c983b15d
--- /dev/null
+++ b/db/migrate/20181025215046_rename_join_table.rb
@@ -0,0 +1,5 @@
+class RenameJoinTable < ActiveRecord::Migration[5.2]
+ def change
+ rename_table :products_categories, :categories_products
+ end
+end
diff --git a/db/order_seeds.csv b/db/order_seeds.csv
new file mode 100644
index 0000000000..8458682fcc
--- /dev/null
+++ b/db/order_seeds.csv
@@ -0,0 +1,5 @@
+name,email,mailing_address,zip_code,cc_number,cc_expiration,cc_cvv,status,total_cost
+Pam,pam89@yahoo.com,No:81 Queen Drive NY,14211,6554323,10/26/2019,322,"pending",3500
+Susan,susan_smith@yahoo.com,18422 Birch Drive WA,19822,2468261,11/20/2020,455,"paid",40000
+Timothy,t_green@hotmail.com,34 5th Avenue VA,98022,34726454,09/23/2021,233,"complete",2800
+David,david_duchovny@hotmail.com,Post Office OR,97033,467542365,10/18/2021,111,"cancelled",30000
diff --git a/db/schema.rb b/db/schema.rb
new file mode 100644
index 0000000000..dec66ce05f
--- /dev/null
+++ b/db/schema.rb
@@ -0,0 +1,90 @@
+# 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: 2018_10_25_215046) do
+
+ # These are extensions that must be enabled in order to support this database
+ enable_extension "plpgsql"
+
+ create_table "categories", force: :cascade do |t|
+ t.string "name"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
+
+ create_table "categories_products", force: :cascade do |t|
+ t.bigint "product_id"
+ t.bigint "category_id"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["category_id"], name: "index_categories_products_on_category_id"
+ t.index ["product_id"], name: "index_categories_products_on_product_id"
+ end
+
+ create_table "orderproducts", 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_orderproducts_on_order_id"
+ t.index ["product_id"], name: "index_orderproducts_on_product_id"
+ end
+
+ create_table "orders", force: :cascade do |t|
+ t.string "name"
+ t.string "email"
+ t.string "mailing_address"
+ t.integer "zip_code"
+ t.bigint "cc_number"
+ t.string "cc_expiration"
+ t.integer "cc_cvv"
+ t.string "status"
+ t.decimal "total_cost", precision: 8, scale: 2
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
+
+ create_table "products", force: :cascade do |t|
+ t.bigint "user_id"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.integer "stock_count"
+ t.decimal "price", precision: 8, scale: 2
+ t.string "photo_url"
+ t.string "description"
+ t.string "name"
+ t.integer "category_id"
+ t.index ["user_id"], name: "index_products_on_user_id"
+ end
+
+ create_table "reviews", force: :cascade do |t|
+ t.string "name"
+ t.integer "rating"
+ t.string "review"
+ t.bigint "product_id"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["product_id"], name: "index_reviews_on_product_id"
+ end
+
+ create_table "users", force: :cascade do |t|
+ t.string "name"
+ t.string "email"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.integer "uid", null: false
+ t.string "provider", null: false
+ end
+
+end
diff --git a/db/seeds.rb b/db/seeds.rb
new file mode 100644
index 0000000000..4afd4086ce
--- /dev/null
+++ b/db/seeds.rb
@@ -0,0 +1,87 @@
+# This file should contain all the record creation needed to seed the database with its default values.
+# The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup).
+#
+# Examples:
+#
+# movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }])
+# Character.create(name: 'Luke', movie: movies.first)
+require 'csv'
+
+USER_FILE = Rails.root.join('db', 'user_seeds.csv')
+
+user_failures = []
+CSV.foreach(USER_FILE, :headers => true) do |row|
+ user = User.new
+ user.name = row['name']
+ user.email = row['email']
+ user.uid = row['uid']
+ user.provider = row['provider']
+ successful = user.save
+ if !successful
+ user_failures << user
+ else
+ puts "User created: #{user.inspect}"
+ end
+end
+
+CATEGORY_FILE = Rails.root.join('db', 'category_seeds.csv')
+category_failures = []
+CSV.foreach(CATEGORY_FILE, :headers => true) do |row|
+ category = Category.new
+ category.name = row['name']
+ successful = category.save
+ if !successful
+ category_failures << category
+ else
+ puts "Category created: #{category.inspect}"
+ end
+end
+
+ORDER_FILE = Rails.root.join('db', 'order_seeds.csv')
+order_failures = []
+CSV.foreach(ORDER_FILE, :headers => true) do |row|
+ order = Order.new
+ order.name = row['name']
+ order.email = row['email']
+ order.mailing_address = row['mailing_address']
+ order.zip_code = row['zip_code']
+ order.cc_number = row['cc_number']
+ order.cc_expiration = row['cc_expiration']
+ order.cc_cvv = row['cc_cvv']
+ order.status = row['status']
+ order.total_cost = row['total_cost']
+ successful = order.save
+ if !successful
+ order_failures << order
+ puts order_failures
+ puts order.errors.messages
+ else
+ puts "Order created: #{order.inspect}"
+ end
+end
+
+CREATURE_FILE = Rails.root.join('db', 'creature_seeds.csv')
+
+creature_failures = []
+CSV.foreach(CREATURE_FILE, :headers => true) do |row|
+ creature = Product.new
+ creature.name = row['name']
+ creature.stock_count = row['stock_count']
+ creature.description = row['description']
+ creature.price = row['price']
+ creature.photo_url = row['photo_url']
+ category = Category.where(name: row['category'])
+ creature.categories << category
+ ids = User.pluck(:id)
+ random_record = User.find(ids.sample)
+ creature.user_id = random_record.id
+ # ids = Category.pluck(:id)
+ # random_record = Category.find(ids.sample)
+ # creature.category_id = random_record.id
+ successful = creature.save
+ if !successful
+ creature_failures << creature
+ else
+ #puts "Creature created: #{creature.inspect}"
+ end
+end
diff --git a/db/user_seeds.csv b/db/user_seeds.csv
new file mode 100644
index 0000000000..3a4807dee8
--- /dev/null
+++ b/db/user_seeds.csv
@@ -0,0 +1,3 @@
+name,email,uid,provider
+Soren Smuggler,topsecret@gmail.com,1,github
+Kylie Muramatsu,dontaskdontell@yahoo.comg,2,github
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/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/storage/.keep b/storage/.keep
new file mode 100644
index 0000000000..e69de29bb2
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/categories_controller_test.rb b/test/controllers/categories_controller_test.rb
new file mode 100644
index 0000000000..21bd684f0f
--- /dev/null
+++ b/test/controllers/categories_controller_test.rb
@@ -0,0 +1,74 @@
+require "test_helper"
+
+describe CategoriesController do
+
+ let (:category_hash) do
+ {
+ category: {
+ name: "Snake",
+ }}
+ end
+
+ let (:wrong_hash) do
+ {
+ category: {
+ name: nil,
+ }}
+ end
+
+describe "index" do
+ it "should get index" do
+ get categories_path
+ must_respond_with :success
+ end
+end
+
+describe "show" do
+ it "will get show for valid ids" do
+ id = categories(:mystical).id
+
+ get category_path(id)
+ must_respond_with :success
+ end
+
+ it "will respond with not_found for invalid ids" do
+ id = categories(:mystical)
+ categories(:mystical).destroy
+
+ get category_path(id)
+ must_respond_with :not_found
+ end
+end
+
+describe "new" do
+ it "will load the new category" do
+ kit =users(:kit)
+ perform_login(kit)
+
+ get new_category_path
+ must_respond_with :success
+ end
+end
+
+
+ describe "create" do
+ it "can create a category" do
+ expect {
+ post categories_path, params: category_hash
+ }.must_change 'Category.count', 1
+
+ must_respond_with :redirect
+
+ expect(Category.last.name).must_equal category_hash[:category][:name]
+ end
+
+ it "cannot create a category with invalid data" do
+ expect {
+ post categories_path, params: wrong_hash
+ }.wont_change 'Category.count'
+
+ must_respond_with :bad_request
+
+ end
+end
+end
diff --git a/test/controllers/orderproducts_controller_test.rb b/test/controllers/orderproducts_controller_test.rb
new file mode 100644
index 0000000000..95fe1f0908
--- /dev/null
+++ b/test/controllers/orderproducts_controller_test.rb
@@ -0,0 +1,59 @@
+require "test_helper"
+
+describe OrderproductsController do
+ describe "update" do
+ it "updates the status to pending or shipped" do
+ orderproduct1 = orderproducts(:orderproduct1)
+ status_hash = {
+ orderproduct: {
+ status: "shipped"
+ }
+ }
+
+ patch orderproduct_path(orderproduct1.id), params: status_hash
+
+ must_respond_with :redirect
+ assert_equal "Status of requested creature has been changed.", flash[:success]
+ updated_orderproduct1 = Orderproduct.find_by(id: orderproduct1.id)
+
+ expect(updated_orderproduct1.status).must_equal status_hash[:orderproduct][:status]
+ expect(orderproduct1.status).wont_equal updated_orderproduct1.status
+ end
+
+ it "doesnt update the status with invalid options" do
+ orderproduct1 = orderproducts(:orderproduct1)
+ status_hash = {
+ orderproduct: {
+ status: "cheese"
+ }
+ }
+
+ patch orderproduct_path(orderproduct1.id), params: status_hash
+
+ must_respond_with :redirect
+ assert_equal 'Status could not be changed.', flash[:warning]
+ updated_orderproduct1 = Orderproduct.find_by(id: orderproduct1.id)
+
+ expect(updated_orderproduct1.status).wont_equal status_hash[:orderproduct][:status]
+ expect(orderproduct1.status).must_equal updated_orderproduct1.status
+ end
+
+ it "only updates the status" do
+ orderproduct1 = orderproducts(:orderproduct1)
+ status_hash = {
+ orderproduct: {
+ quantity: 1
+ }
+ }
+
+ patch orderproduct_path(orderproduct1.id), params: status_hash
+
+ must_respond_with :redirect
+ assert_equal 'Status could not be changed.', flash[:warning]
+ updated_orderproduct1 = Orderproduct.find_by(id: orderproduct1.id)
+
+ expect(updated_orderproduct1.status).wont_equal status_hash[:orderproduct][:quantity]
+ expect(orderproduct1.quantity).must_equal updated_orderproduct1.quantity
+ 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..32aca11bd9
--- /dev/null
+++ b/test/controllers/orders_controller_test.rb
@@ -0,0 +1,242 @@
+require "test_helper"
+
+describe OrdersController do
+
+ describe "new" do
+ it "succeeds" do
+ get new_order_path
+
+ must_respond_with :success
+ end
+ end
+
+ describe "create" do
+ let (:order_hash) do
+ {
+ order: {
+ name: 'Susie Smiths',
+ email: 'testemail@gmail.com',
+ mailing_address: '4150 Delridge Way SW',
+ zip_code: 44903,
+ cc_number: 8275928304958372,
+ cc_expiration: '04/21',
+ cc_cvv: 843,
+ status: 'pending',
+ total_cost: 8000
+ }
+ }
+ end
+
+ let (:bad_params) do
+ {
+ order: {
+ name: nil,
+ email: nil,
+ mailing_address: nil,
+ zip_code: 44903,
+ cc_number: 8275928304958372,
+ cc_expiration: '04/21',
+ cc_cvv: 843,
+ status: nil,
+ total_cost: 8000
+ }
+ }
+ end
+
+ before do
+ @lamb = products(:lamb)
+ @quantity_hash = { quantity: "3" }
+ @duckling = products(:duckling)
+ @quantity_hash2 = { quantity: "2" }
+ @unicorn = products(:unicorn)
+ @quantity_hash3 = { quantity: "1" }
+ end
+
+ it "creates an order with paid status" do
+ # adds things so session[:cart]
+ post add_to_cart_path(@lamb.id), params: @quantity_hash
+ post add_to_cart_path(@duckling.id), params: @quantity_hash2
+
+ expect {
+ post orders_path, params: order_hash
+ }.must_change 'Order.count', 1
+
+ must_respond_with :redirect
+ expect(flash[:success]).must_equal "Adoption request successfully placed! (Order ##{Order.last.id})"
+ expect(Order.last.status).must_equal 'paid'
+ end
+
+ it "fails to create order when a product in the session goes out of stock" do
+
+ post add_to_cart_path(@lamb.id), params: @quantity_hash
+ post add_to_cart_path(@duckling.id), params: @quantity_hash2
+ post add_to_cart_path(@unicorn.id), params: @quantity_hash3
+ @unicorn.update(stock_count: 0)
+ @unicorn.save
+
+ expect {
+ post orders_path, params: order_hash
+ }.wont_change 'Order.count'
+
+ expect(flash[:danger]).wont_be_nil
+ must_respond_with :redirect
+ end
+
+ it "won't create an order without the needed buyers information" do
+ post add_to_cart_path(@lamb.id), params: @quantity_hash
+ post add_to_cart_path(@duckling.id), params: @quantity_hash2
+
+ expect {
+ post orders_path, params: bad_params
+ }.wont_change 'Order.count'
+
+ expect(flash[:messages]).must_include :name, :email
+ must_respond_with :bad_request
+ #won't create orphaned Orderproducts
+ expect(Orderproduct.last.order_id).wont_be_nil
+ end
+ end
+
+ describe "new" do
+ it "succeeds" do
+ get new_order_path
+
+ must_respond_with :success
+ end
+ end
+
+ describe "show" do
+ it "displays a given order" do
+ id = orders(:complete_order).id
+ get order_path(id)
+ must_respond_with :success
+ end
+ end
+
+ describe "update" do
+ let (:order_params) do
+ {
+ order: {
+ name: 'No OrderProducts McGee',
+ email: 'testemail@gmail.com',
+ mailing_address: '4150 Delridge Way SW',
+ zip_code: 44903,
+ cc_number: 8275928304958372,
+ cc_expiration: '04/21',
+ cc_cvv: 843,
+ status: 'completed',
+ total_cost: 8000
+ }
+ }
+ end
+ let (:bad_params) do
+ {
+ order: {
+ name: nil,
+ email: nil,
+ mailing_address: nil,
+ zip_code: 44903,
+ cc_number: 8275928304958372,
+ cc_expiration: '04/21',
+ cc_cvv: 843,
+ status: nil,
+ total_cost: 8000
+ }
+ }
+ end
+ it "succeeds in updating order status" do
+ id = orders(:complete_order).id
+
+ expect {
+ patch order_path(id), params: order_params
+ }.wont_change 'Order.count'
+
+ must_respond_with :redirect
+ must_redirect_to order_path(id)
+
+ expect(Order.find(id).status).must_equal order_params[:order][:status]
+ end
+
+ it "renders bad_request when given nil update data" do
+ id = orders(:complete_order).id
+
+ expect {
+ patch order_path(id), params: bad_params
+ }.wont_change 'Order.count'
+
+ must_respond_with :redirect
+ expect(Order.find(id).name).must_equal "Monique Marie"
+ expect(flash[:danger]).must_equal "Adoption request was not updated."
+ end
+ end
+ # => "orders", "action"=>"show", "id"=>"5"} permitted: false>
+
+ describe "search" do
+ it "allows a user to input an order number and display order" do
+
+ complete_order = orders(:complete_order)
+ order_num_params = {"id"=> complete_order.id.to_s}
+
+ get search_orders_path, params: order_num_params
+ must_respond_with get order_path(complete_order.id)
+ end
+
+ it "fails with invalid id" do
+
+ order_num_params = {"id"=> "hello"}
+
+ get search_orders_path, params: order_num_params
+ assert_response :not_found
+ expect(flash.keys).must_equal ["danger"]
+ end
+ end
+
+ describe "fulfillment methods" do
+ let(:tan) { users(:tan) }
+
+
+ it "blocks all fulfillment pages if user is not signed in" do
+ get get_orders_path
+ must_redirect_to root_path
+ expect(flash[:danger]).must_equal "Sorry, the fulfillment page is only for creature rescuers."
+
+ get cancelled_orders_path
+ must_redirect_to root_path
+
+ get paid_orders_path
+ must_redirect_to root_path
+
+ get completed_orders_path
+ must_redirect_to root_path
+ end
+
+ it "retrieves all orders and total cost" do
+ expect {perform_login(tan)}.wont_change('User.count')
+ get get_orders_path
+
+ must_respond_with :success
+ end
+
+ it "retrieves paid orders and total cost" do
+ expect {perform_login(tan)}.wont_change('User.count')
+ get paid_orders_path
+
+ must_respond_with :success
+ end
+
+ it "retrieves cancelled orders and total cost" do
+ expect {perform_login(tan)}.wont_change('User.count')
+ get cancelled_orders_path
+
+ must_respond_with :success
+ end
+
+ it "retrieves cancelled orders and total cost" do
+ expect {perform_login(tan)}.wont_change('User.count')
+ get completed_orders_path
+
+ must_respond_with :success
+ end
+
+ end
+end
diff --git a/test/controllers/products_controller_test.rb b/test/controllers/products_controller_test.rb
new file mode 100644
index 0000000000..c78b983b2a
--- /dev/null
+++ b/test/controllers/products_controller_test.rb
@@ -0,0 +1,202 @@
+require "test_helper"
+
+describe ProductsController do
+
+ describe "index" do
+ it "should get index" do
+ get products_path
+ must_respond_with :success
+ end
+ end
+
+ describe "show" do
+ it "will get show for valid ids" do
+ id = products(:lamb).id
+ get product_path(id)
+
+ must_respond_with :success
+ end
+
+ it "will respond with not_found for invalid ids" do
+ id = products(:lamb)
+ products(:lamb).destroy
+
+ get product_path(id)
+
+ must_respond_with :not_found
+ end
+ end
+
+ describe "new" do
+ it "will load the new product" do
+ kit =users(:kit)
+
+ perform_login(kit)
+
+ get new_product_path
+ must_respond_with :success
+ end
+ end
+
+ describe "create" do
+ it "can create a product" do
+ tan =users(:tan)
+
+ perform_login(tan)
+ product_hash = {
+ product: {
+ name: "Tiger",
+ stock_count: 10,
+ description: "ferocious",
+ price: 1100.0,
+ user_id: users(:tan).id,
+ photo_url: "https://i.imgur.com/NyKcY9y.jpg"
+ }}
+
+ expect {
+ post products_path, params: product_hash
+ }.must_change 'Product.count', 1
+
+ must_respond_with :redirect
+
+ expect(Product.last.name).must_equal product_hash[:product][:name]
+ end
+
+ it "will not create a product with invalid params" do
+ tan = users(:tan)
+
+ perform_login(tan)
+ bad_hash = {
+ product: {
+ name: "Zebra",
+ stock_count: 8,
+ description: "not cute",
+ price: nil,
+ user_id: users(:tan).id,
+ photo_url: "https://i.imgur.com/NyKcY9y.jpg"
+ }}
+ expect {
+ post products_path, params: bad_hash
+ }.wont_change 'Product.count'
+
+ must_respond_with :bad_request
+
+ end
+ end
+
+describe "update" do
+
+ it "will update a product with a valid post request" do
+ tan = users(:tan)
+ perform_login(tan)
+
+ product_hash = {
+ product: {
+ name: 'Lamb',
+ stock_count: 10,
+ description: "cute",
+ price: 100.0,
+ user_id: users(:tan).id,
+ photo_url: "https://i.imgur.com/NyKcY9y.jpg"
+ }}
+
+ id = products(:lamb).id
+ expect {
+ patch product_path(id), params: product_hash
+ }.wont_change 'Product.count'
+
+ must_respond_with :redirect
+
+ product = Product.find_by(id: id)
+ product.reload
+ must_respond_with :found
+ must_respond_with :redirect
+ expect(product.name).must_equal product_hash[:product][:name]
+ expect(product.stock_count).must_equal product_hash[:product][:stock_count]
+ expect(product.description).must_equal product_hash[:product][:description]
+ expect(product.price).must_equal product_hash[:product][:price]
+ expect(product.user_id).must_equal product_hash[:product][:user_id]
+ expect(product.photo_url).must_equal product_hash[:product][:photo_url]
+ end
+
+ it "will not update if the params are invalid" do
+
+ tan =users(:tan)
+
+ perform_login(tan)
+ bad_hash = {
+ product: {
+ name: "lamb",
+ stock_count: 8,
+ description: "not cute",
+ user_id: users(:tan).id,
+ photo_url: "https://i.imgur.com/NyKcY9y.jpg"
+ }}
+
+ id = products(:lamb).id
+ original_product = products(:lamb)
+ bad_hash[:product][:name] = nil # invalid id
+ expect {
+ patch product_path(id), params: bad_hash
+ }.wont_change 'Product.count'
+
+ must_respond_with :bad_request
+ product = Product.find_by(id: id)
+ expect(product.name).must_equal original_product.name
+ expect(product.stock_count).must_equal original_product.stock_count
+ end
+
+ it "will respond with not_found for invalid ids" do
+ tan = users(:tan)
+ perform_login(tan)
+
+ product_hash = {
+ product: {
+ name: 'Lamb',
+ stock_count: 10,
+ description: "cute",
+ price: 100.0,
+ user_id: users(:tan).id,
+ photo_url: "https://i.imgur.com/NyKcY9y.jpg"
+ }}
+
+ id = -1
+
+ expect {
+ patch product_path(id), params: product_hash
+ }.wont_change 'Product.count'
+
+ must_respond_with :not_found
+ end
+
+end
+
+describe "tests require_product_owner" do
+ it "does not allow non product owner to update product" do
+ kit =users(:kit)
+ perform_login(kit)
+
+ product_hash = {
+ product: {
+ name: 'Lamb',
+ stock_count: 10,
+ description: "cute",
+ price: 100.0,
+ user_id: users(:kit).id,
+ photo_url: "https://i.imgur.com/NyKcY9y.jpg"
+ }}
+
+ id = products(:lamb).id
+ original_product = products(:lamb)
+ expect {
+ patch product_path(id), params: product_hash
+ }.wont_change 'Product.count'
+
+ must_respond_with :redirect
+ product = Product.find_by(id: id)
+ expect(product.name).must_equal original_product.name
+ expect(product.stock_count).must_equal original_product.stock_count
+ 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..f2ab842cfb
--- /dev/null
+++ b/test/controllers/reviews_controller_test.rb
@@ -0,0 +1,82 @@
+require "test_helper"
+
+describe ReviewsController do
+ describe "new" do
+
+ before do
+ @goat = products(:goat)
+ end
+
+ it "succeeds" do
+ get new_product_review_path(@goat .id)
+ must_respond_with :success
+ end
+ end
+
+ describe "create" do
+ before do
+ @goat = products(:goat)
+ end
+
+ it "creates a review with valid data" do
+ review_hash = {
+ review: {
+ name: "Jane",
+ rating: 5,
+ review: "Best goat ever!",
+ product_id: @goat.id
+ }
+ }
+
+ expect {
+ post product_reviews_path(@goat.id), params: review_hash
+ }.must_change 'Review.count', 1
+
+ must_respond_with :redirect
+ assert_equal 'Thanks for leaving a review!', flash[:success]
+
+ expect(Review.last.name).must_equal review_hash[:review][:name]
+ expect(Review.last.rating).must_equal review_hash[:review][:rating]
+ expect(Review.last.review).must_equal review_hash[:review][:review]
+ end
+
+ it "fails to create a review with invalid data" do
+ review_hash = {
+ review: {
+ name: "Jane",
+ rating: "Hello",
+ review: "Best goat ever!",
+ product_id: "cheese"
+ }
+ }
+
+ expect {
+ post product_reviews_path(@goat.id), params: review_hash
+ }.wont_change 'Review.count'
+
+ must_respond_with :redirect
+ assert_equal 'Review unsuccessful.', flash[:warning]
+ end
+
+ it "a seller can't leave a review for their own product" do
+ user = users(:tan)
+ perform_login(user)
+ review_hash = {
+ review: {
+ name: "Tan",
+ rating: 5,
+ review: "Best goat ever!",
+ product_id: @goat.id
+ }
+ }
+
+ expect {
+ post product_reviews_path(@goat.id), params: review_hash
+ }.wont_change 'Review.count'
+
+ must_respond_with :redirect
+ assert_equal "You cannot review your own creatures!", flash[:warning]
+ end
+ end
+
+end
diff --git a/test/controllers/sessions_controller_test.rb b/test/controllers/sessions_controller_test.rb
new file mode 100644
index 0000000000..70dad4c779
--- /dev/null
+++ b/test/controllers/sessions_controller_test.rb
@@ -0,0 +1,218 @@
+require "test_helper"
+describe SessionsController do
+ describe 'create' do
+ let(:kit) { users(:kit) }
+
+ it "logs in an exiting user and redirects to the root route" do
+ expect {perform_login(kit)}.wont_change('User.count')
+
+ must_redirect_to root_path
+ expect(session[:user_id]).must_equal kit.id
+ end
+
+ it "creates an account for a new user and redirects to the root route" do
+ stub_auth_hash!(
+ uid: '12343',
+ provider: 'github',
+ info: {
+ email: 'test@example.com',
+ name: 'test'
+ }
+ )
+
+ expect { get login_path('github') }.must_change('User.count', 1)
+
+ must_redirect_to root_path
+ expect(session[:user_id]).wont_be_nil
+ end
+
+ it "doesn't create an account for a new user with invalid data" do
+ stub_auth_hash!(
+ uid: '12343',
+ provider: 'github',
+ info: {
+ email: kit.email,
+ name: kit.name
+ }
+ )
+
+ expect { get login_path('github') }.wont_change('User.count', 1)
+
+ expect(flash.keys).must_equal ["danger"]
+ expect(session[:user_id]).must_be_nil
+ must_respond_with :redirect
+ end
+ end
+
+ describe "destroy" do
+ before do
+ @kit = users(:kit)
+ perform_login(@kit)
+ end
+
+ it "makes session[:user_id] nil " do
+ delete logout_path
+
+ expect(session[:user_id]).must_be_nil
+ assert_equal 'Successfully logged out', flash[:success]
+ must_respond_with :redirect
+ end
+
+ it "the user is logged out " do
+ expect(session[:user_id]).must_equal @kit.id
+
+ delete logout_path
+
+ expect(session[:user_id]).must_be_nil
+ assert_equal 'Successfully logged out', flash[:success]
+ must_respond_with :redirect
+ end
+
+ it "the user can no longer access certain pages" do
+ delete logout_path
+
+ get get_orders_path
+ must_redirect_to root_path
+ expect(flash[:danger]).must_equal "Sorry, the fulfillment page is only for creature rescuers."
+ end
+ end
+
+ describe 'add_to_cart' do
+ before do
+ @lamb = products(:lamb)
+ @quantity_hash = { quantity: "3" }
+ end
+
+ it "adds a new product to a cart" do
+ #add to cart
+ post add_to_cart_path(@lamb.id), params: @quantity_hash
+
+ #check quantity against quantity_hash
+ expect(session[:cart]).must_include @lamb.id => @quantity_hash[:quantity].to_i
+ expect(session[:cart].length).must_equal 1
+
+ must_respond_with :redirect
+ end
+
+ it "updates the quantity of the cart" do
+ #add to cart
+ post add_to_cart_path(@lamb.id), params: @quantity_hash
+
+ #check quantity against quantity_hash
+ expect(session[:cart].first.values[0]).must_equal @quantity_hash[:quantity].to_i
+
+ #add to cart again
+ post add_to_cart_path(@lamb.id), params: @quantity_hash
+
+ #check quantity against quantity_hash times two
+ expect(session[:cart].first.values[0]).must_equal @quantity_hash[:quantity].to_i * 2
+ must_respond_with :redirect
+ end
+
+ it "won't add to cart if there isn't enough stock or invalid stock" do
+ large_quantity_hash = { quantity: "20" }
+ post add_to_cart_path(@lamb.id), params: large_quantity_hash
+ expect(session[:cart]).must_equal []
+
+ invalid_quantity_hash = {quantity: "hello"}
+ post add_to_cart_path(@lamb.id), params: invalid_quantity_hash
+ expect(session[:cart]).must_equal []
+ end
+
+ it "won't add item to cart if there isn't enough stock due to that item already being in cart" do
+ quantity_hash = { quantity: "5" }
+ post add_to_cart_path(@lamb.id), params: quantity_hash
+
+ large_quantity_hash = { quantity: "6" }
+ post add_to_cart_path(@lamb.id), params: large_quantity_hash
+ expect(flash[:warning]).wont_be_nil
+ expect(session[:cart]).must_equal [{@lamb.id.to_s => 5}]
+ end
+
+ it "won't add to cart if the product can't be found" do
+ quantity_hash = { quantity: "5" }
+ id = "hello"
+
+ post add_to_cart_path(id), params: quantity_hash
+
+ expect(session[:cart]).must_equal []
+ assert_response :not_found
+ end
+ end
+
+ describe 'remove_from_cart' do
+ before do
+ @lamb = products(:lamb)
+ @goat = products(:goat)
+ @quantity_hash = { quantity: "3" }
+ post add_to_cart_path(@lamb.id), params: @quantity_hash
+ end
+
+ it "removes item from cart" do
+ expect(session[:cart].length).must_equal 1
+
+ get remove_from_cart_path(@lamb.id)
+ expect(session[:cart].length).must_equal 0
+ end
+
+ it "removes specific item from cart" do
+ post add_to_cart_path(@goat.id), params: @quantity_hash
+
+ expect(session[:cart].length).must_equal 2
+
+ get remove_from_cart_path(@lamb.id)
+ expect(session[:cart].length).must_equal 1
+ expect(session[:cart]).wont_include @lamb.id => @quantity_hash[:quantity].to_i
+ expect(session[:cart]).must_include @goat.id.to_s => @quantity_hash[:quantity].to_i
+ end
+ end
+
+
+ describe 'update_quantity' do
+ before do
+ @lamb = products(:lamb)
+ @quantity_hash = { quantity: "3" }
+ @original_quantity = @quantity_hash[:quantity].to_i
+ post add_to_cart_path(@lamb.id), params: @quantity_hash
+ end
+
+ it "updates the quantity if there is enough stock" do
+ new_quantity_hash = { quantity: "5" }
+
+ patch update_cart_path(@lamb.id), params: new_quantity_hash
+ expect(session[:cart].first.values[0].must_equal new_quantity_hash[:quantity].to_i)
+ end
+
+ it "doesn't update the quantity if there isn't enough stock" do
+ new_quantity_hash = { quantity: "50" }
+
+ expect {
+ patch update_cart_path(@lamb.id), params: new_quantity_hash
+ }.wont_change 'session[:cart].first.values[0]'
+
+ expect(session[:cart].first.values[0]).must_equal @original_quantity
+ end
+ end
+
+ describe 'cart_view' do
+ before do
+ @lamb = products(:lamb)
+ @quantity_hash = { quantity: "3" }
+ end
+
+ it "succeeds when there are items in cart" do
+ post add_to_cart_path(@lamb.id), params: @quantity_hash
+
+ get cart_path
+
+ must_respond_with :success
+ end
+
+ it "succeeds when there aren't items in cart" do
+ get cart_path
+
+ must_respond_with :success
+ end
+ end
+
+end
diff --git a/test/controllers/users_controller_test.rb b/test/controllers/users_controller_test.rb
new file mode 100644
index 0000000000..a55bcc950e
--- /dev/null
+++ b/test/controllers/users_controller_test.rb
@@ -0,0 +1,38 @@
+require 'test_helper'
+
+describe UsersController do
+
+ describe "index" do
+ it "succeeds when there are users" do
+ get users_path
+
+ must_respond_with :success
+ end
+
+ it "succeeds when there are no users" do
+ users = User.all
+ User.all.each do |user|
+ user.destroy
+ end
+
+ get users_path
+ must_respond_with :success
+ end
+ end
+
+ describe "show" do
+ it "succeeds for an existing user" do
+ id = users(:tan).id
+
+ get user_path(id)
+
+ must_respond_with :success
+ end
+
+ it "renders 404 not_found for a bogus user ID" do
+ id = -1
+ get user_path(id)
+ must_respond_with :not_found
+ end
+ end
+end
diff --git a/test/fixtures/.keep b/test/fixtures/.keep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/test/fixtures/categories.yml b/test/fixtures/categories.yml
new file mode 100644
index 0000000000..503bc1e4ec
--- /dev/null
+++ b/test/fixtures/categories.yml
@@ -0,0 +1,13 @@
+# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
+
+one:
+ name: MyString
+
+two:
+ name: MyString
+
+mystical:
+ name: Mystical
+
+dangerous:
+ name: Dangerous
diff --git a/test/fixtures/files/.keep b/test/fixtures/files/.keep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/test/fixtures/orderproducts.yml b/test/fixtures/orderproducts.yml
new file mode 100644
index 0000000000..ae4948b835
--- /dev/null
+++ b/test/fixtures/orderproducts.yml
@@ -0,0 +1,43 @@
+# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
+
+orderproduct1:
+ order: complete_order
+ product: lamb
+ quantity: 3
+ status: pending
+
+orderproduct2:
+ order: complete_order
+ product: lamb
+ quantity: 3
+ status: pending
+
+orderproduct3:
+ order: complete_order
+ product: lamb
+ quantity: 3
+ status: shipped
+
+orderproduct4:
+ order: complete_order2
+ product: lamb
+ quantity: 2
+ status: shipped
+
+orderproduct5:
+ order: complete_order3
+ product: unicorn
+ quantity: 1
+ status: shipped
+
+orderproduct6:
+ order: complete_order
+ product: goat
+ quantity: 2
+ status: pending
+
+orderproduct6:
+ order: complete_order
+ product: dragon
+ quantity: 1
+ status: shipped
diff --git a/test/fixtures/orders.yml b/test/fixtures/orders.yml
new file mode 100644
index 0000000000..138b547f49
--- /dev/null
+++ b/test/fixtures/orders.yml
@@ -0,0 +1,45 @@
+# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
+
+complete_order:
+ name: Monique Marie
+ email: testemail@gmail.com
+ mailing_address: 4150 Delridge Way SW
+ zip_code: 44903
+ cc_number: 8275928304958372
+ cc_expiration: 04/21
+ cc_cvv: 843
+ status: paid
+ total_cost: 8000.0
+
+pending_order:
+ name: nil
+ email: nil
+ mailing_address: nil
+ zip_code: nil
+ cc_number: nil
+ cc_expiration: nil
+ cc_cvv: nil
+ status: pending
+ total_cost: 0.0
+
+complete_order2:
+ name: Test User2
+ email: testemail@gmail.com
+ mailing_address: 4150 Delridge Way SW
+ zip_code: 44903
+ cc_number: 8275928304958372
+ cc_expiration: 04/21
+ cc_cvv: 843
+ status: completed
+ total_cost: 8000.0
+
+complete_order3:
+ name: Test User3
+ email: testemail@gmail.com
+ mailing_address: 4150 Delridge Way SW
+ zip_code: 44903
+ cc_number: 8275928304958372
+ cc_expiration: 04/21
+ cc_cvv: 843
+ status: cancelled
+ total_cost: 8000.0
diff --git a/test/fixtures/products.yml b/test/fixtures/products.yml
new file mode 100644
index 0000000000..db98028d5f
--- /dev/null
+++ b/test/fixtures/products.yml
@@ -0,0 +1,54 @@
+# 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
+#
+product1:
+ name: nil
+ stock_count: nil
+ description: nil
+ price: 100.0
+ user: tan
+ photo_url: https://i.imgur.com/NyKcY9y.jpg
+
+
+lamb:
+ name: Lamb
+ stock_count: 10
+ description: cute
+ price: 100.0
+ user: tan
+ photo_url: https://i.imgur.com/NyKcY9y.jpg
+
+goat:
+ name: Goat
+ stock_count: 15
+ description: cute
+ price: 200.0
+ user: tan
+ photo_url: https://i.imgur.com/NyKcY9y.jpg
+
+duckling:
+ name: Duckling
+ stock_count: 8
+ description: cute
+ price: 200.0
+ user: tan
+ photo_url: https://i.imgur.com/NyKcY9y.jpg
+
+unicorn:
+ name: Unicorn
+ stock_count: 1
+ description: ultra-rare
+ price: 500.0
+ user: tan
+ photo_url: https://i.imgur.com/NyKcY9y.jpg
+
+dragon:
+ name: Dragoonie
+ stock_count: 1
+ description: breathes fire!
+ price: 100.0
+ user: kit
+ photo_url: https://i.imgur.com/NyKcY9y.jpg
diff --git a/test/fixtures/reviews.yml b/test/fixtures/reviews.yml
new file mode 100644
index 0000000000..18ef80472c
--- /dev/null
+++ b/test/fixtures/reviews.yml
@@ -0,0 +1,13 @@
+# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
+
+one:
+ name: Maria
+ rating: 5
+ review: Best lamb ever
+ product: lamb
+
+two:
+ name: Bess
+ rating: 1
+ review: Worst lamb ever
+ product: lamb
diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml
new file mode 100644
index 0000000000..ef11c7d57a
--- /dev/null
+++ b/test/fixtures/users.yml
@@ -0,0 +1,13 @@
+# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
+
+kit:
+ name: kit
+ email: kit@gmail.com
+ uid: 2378
+ provider: github
+
+tan:
+ name: tan
+ email: tan@gmail.com
+ uid: 8769
+ provider: github
diff --git a/test/helpers/.keep b/test/helpers/.keep
new file mode 100644
index 0000000000..e69de29bb2
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/category_test.rb b/test/models/category_test.rb
new file mode 100644
index 0000000000..5962fb68c5
--- /dev/null
+++ b/test/models/category_test.rb
@@ -0,0 +1,51 @@
+require "test_helper"
+
+describe Category do
+ describe "validations" do
+ before do
+ category = Category.first
+ @category = Category.new(name: "test name")
+ end
+
+ it "can be created with all required fields" do
+ result = @category.valid?
+ result.must_equal true
+ end
+
+ it "is invalid without a name" do
+ @category.name = nil
+ result = @category.valid?
+ result.must_equal false
+ end
+
+ it "is invalid with a duplicate name" do
+ dup_category = Category.first
+ @category.name = dup_category.name
+ result = @category.valid?
+ result.must_equal false
+ end
+ end
+
+ describe "relationships" do
+ before do
+ @category = Category.new(name: 'test name')
+ end
+
+ it "connects products and product_ids" do
+ product = Product.first
+ @category.products << product
+ @category.product_ids.must_include product.id
+ end
+ end
+
+ describe 'self.category_list' do
+ it "can return all categories" do
+
+ result = Category.category_list
+ puts result
+
+ result.length.must_equal 4
+ end
+ end
+
+end
diff --git a/test/models/order_test.rb b/test/models/order_test.rb
new file mode 100644
index 0000000000..a3dce88781
--- /dev/null
+++ b/test/models/order_test.rb
@@ -0,0 +1,160 @@
+require "test_helper"
+
+describe Order do
+ describe "validations" do
+ let(:order) { Order.create(status: "pending") }
+
+ it "must be invalid without customer info provided" do
+ order.valid?.must_equal false
+ end
+
+ it "will not update order without all customer info" do
+ order2 = orders(:complete_order)
+
+ order2.valid?.must_equal true
+ order2.update(name:nil)
+ order2.valid?.must_equal false
+ expect(order2.save).must_equal false
+ order2.update(name:"Monique Marie")
+ order2.valid?.must_equal true
+
+ order2.update(email:nil)
+ order2.valid?.must_equal false
+ expect(order2.save).must_equal false
+ order2.update(email:"testemail@gmail.com")
+ order2.valid?.must_equal true
+
+ order2.update(mailing_address:nil)
+ order2.valid?.must_equal false
+ expect(order2.save).must_equal false
+ order2.update(mailing_address:"4150 Delridge Way SW")
+ order2.valid?.must_equal true
+
+ order2.update(zip_code:nil)
+ order2.valid?.must_equal false
+ expect(order2.save).must_equal false
+ order2.update(zip_code:44903)
+ order2.valid?.must_equal true
+
+ order2.update(cc_number:nil)
+ order2.valid?.must_equal false
+ expect(order2.save).must_equal false
+ order2.update(cc_number:8275928304958372)
+ order2.valid?.must_equal true
+
+ order2.update(cc_expiration:nil)
+ order2.valid?.must_equal false
+ expect(order2.save).must_equal false
+ order2.update(cc_expiration:"04/21")
+ order2.valid?.must_equal true
+
+ order2.update(cc_cvv:nil)
+ order2.valid?.must_equal false
+ expect(order2.save).must_equal false
+ order2.update(cc_cvv:843)
+ order2.valid?.must_equal true
+ end
+ end
+
+ describe "relations" do
+ let(:order) {
+ order = orders(:pending_order)
+ }
+
+ it "must have one or many orderproducts to update order" do
+
+ order.update(name:"No OrderProducts McGee", email:"testemail@gmail.com",
+ mailing_address:"4150 Delridge Way SW", zip_code:44903,
+ cc_number: 8275928304958372, cc_expiration: "04/21", cc_cvv: 843,
+ status: "paid", total_cost: 8000)
+ #no orderproducts attached
+ expect(order.save).must_equal false
+
+ #adding orderproduct to order
+ orderproduct1 = orderproducts(:orderproduct1)
+ order.orderproducts << orderproduct1
+ expect(order.save).must_equal true
+ expect(order.orderproducts.length).must_be :>, 0
+ end
+ end
+
+ describe "order model methods" do
+ describe "Order#order_total" do
+ it "will tally the cost of products for a given order" do
+ order = orders(:complete_order)
+
+ expect(order.order_total).must_equal 0.1e4
+ end
+ end
+
+ describe "Order#reduce_stock" do
+ it "will reduce product stock related to a given order" do
+
+ end
+ end
+
+ describe "Order#find_orderproducts" do
+ it "can find orderproducts when a status is provided" do
+ tan = users(:tan)
+
+ orderproducts = Order.find_orderproducts(tan, nil)
+
+ expect(orderproducts).wont_be_nil
+ expect(orderproducts.length).must_equal 5
+ expect(orderproducts.first.product.user).must_equal tan
+ end
+
+ it "can find orderproducts when a status is not provided" do
+ tan = users(:tan)
+
+ orderproducts = Order.find_orderproducts(tan, "paid")
+
+ expect(orderproducts).wont_be_nil
+ expect(orderproducts.length).must_equal 3
+ expect(orderproducts.first.product.user).must_equal tan
+ end
+
+ it "throws an argument error when no user is provided to method" do
+ assert_raises(NameError) {orderproducts = Order.find_orderproducts(nil, "paid")}
+ end
+
+ end
+
+ describe "Order#count_orders" do
+ it "retrieves a count of orders of a certain status" do
+ tan = users(:tan)
+
+ count = Order.count_orders(tan, "paid")
+ expect(count).must_be :<=, 4
+ end
+
+ it "retrieves a count of orders when given no status" do
+ tan = users(:tan)
+
+ count = Order.count_orders(tan, nil)
+ expect(count).must_equal 5
+ end
+ end
+
+ describe "Order#products_sold_total" do
+ it "returns the revenue for the orders when given orderproducts" do
+ tan = users(:tan)
+
+ orderproducts = Order.find_orderproducts(tan, "paid")
+
+ money = Order.products_sold_total(tan, orderproducts)
+
+ expect(money).must_equal 0.9e3
+ end
+
+ it "returns an error when no user is provided" do
+ tan = users(:tan)
+ orderproducts = Order.find_orderproducts(tan, "paid")
+
+ assert_raises(NameError) {Order.products_sold_total(nil, orderproducts)}
+ end
+
+ end
+
+ end
+end
diff --git a/test/models/orderproduct_test.rb b/test/models/orderproduct_test.rb
new file mode 100644
index 0000000000..b48a1b4a3a
--- /dev/null
+++ b/test/models/orderproduct_test.rb
@@ -0,0 +1,135 @@
+require "test_helper"
+describe Orderproduct do
+
+ describe "relationships" do
+ before do
+ @orderproduct1 = orderproducts(:orderproduct1)
+ end
+
+ it "has a product" do
+ @orderproduct1.must_respond_to :product
+ @orderproduct1.product.must_be_kind_of Product
+ end
+
+ it "has an order" do
+ @orderproduct1.must_respond_to :order
+ @orderproduct1.order.must_be_kind_of Order
+ end
+
+ end
+
+ describe "validations" do
+
+ before do
+ @goat = products(:goat)
+ @order = orders(:complete_order)
+ end
+
+ it "allows the two valid statuses" do
+ valid_statuses = ['pending', 'shipped']
+ valid_statuses.each do |status|
+ orderproduct = Orderproduct.new(status: status, quantity: 1, order_id: @order.id, product_id: @goat.id)
+ orderproduct.valid?.must_equal true
+ end
+ end
+
+ it "rejects invalid statuses" do
+ invalid_statuses = ['cat', 'dog', 'phd thesis', 1337, nil]
+ invalid_statuses.each do |status|
+ orderproduct = Orderproduct.new(status: status, quantity: 1, order_id: @order.id, product_id: @goat.id)
+ orderproduct.valid?.must_equal false
+ orderproduct.errors.messages.must_include :status
+ end
+ end
+
+ it "requires a status" do
+ orderproduct = Orderproduct.new(quantity: 1, order_id: @order.id, product_id: @goat.id)
+ orderproduct.valid?.must_equal false
+ orderproduct.errors.messages.must_include :status
+ end
+
+ it "requires a quantity" do
+ orderproduct = Orderproduct.new(status: "pending", order_id: @order.id, product_id: @goat.id)
+ orderproduct.valid?.must_equal false
+ orderproduct.errors.messages.must_include :quantity
+ end
+
+ it "a quantity needs to be an integer" do
+ orderproduct = Orderproduct.new(status: "pending", quantity: "hello", order_id: @order.id, product_id: @goat.id)
+ orderproduct.valid?.must_equal false
+ orderproduct.errors.messages.must_include :quantity
+ end
+
+ it "a quantity needs to be greater than 0" do
+ orderproduct = Orderproduct.new(status: "pending", quantity: -1, order_id: @order.id, product_id: @goat.id)
+ orderproduct.valid?.must_equal false
+ orderproduct.errors.messages.must_include :quantity
+ end
+
+ it "a quantity needs to be equal or less than an orderproduct's product's stock count" do
+ orderproduct = Orderproduct.create(status: "pending", quantity: -1, order_id: @order.id, product_id: @goat.id)
+ orderproduct.valid?.must_equal false
+ orderproduct.errors.messages.must_include :quantity
+
+ orderproduct = Orderproduct.new(status: "pending", quantity: 100, order_id: @order.id, product_id: @goat.id)
+ orderproduct.valid?.must_equal false
+ orderproduct.errors.messages.must_include :quantity
+
+ orderproduct = Orderproduct.new(status: "pending", quantity: 15, order_id: @order.id, product_id: @goat.id)
+ orderproduct.valid?.must_equal true
+
+ orderproduct = Orderproduct.new(status: "pending", quantity: 5, order_id: @order.id, product_id: @goat.id)
+ orderproduct.valid?.must_equal true
+ end
+
+ it "requires an order_id" do
+ orderproduct = Orderproduct.new(quantity: 1, status: "pending", product_id: @goat.id)
+ orderproduct.valid?.must_equal false
+ orderproduct.errors.messages.must_include :order_id
+ end
+
+ it "requires an product_id" do
+ orderproduct = Orderproduct.new(quantity: 1, status: "pending", order_id: @order.id)
+ orderproduct.valid?.must_equal false
+ orderproduct.errors.messages.must_include :product_id
+ end
+
+ end
+
+ describe "create_product_orders" do
+ before do
+ @order = Order.create(name: "Judy Poacher", email: "fake@fake.com", mailing_address: "1234 USA Main Street", zip_code: 12345, cc_number: 8295838304727592, cc_expiration: 1155, cc_cvv: 476, status: "pending", total_cost: 700.00)
+ @lamb = products(:lamb)
+ @goat = products(:lamb)
+ @session_cart = [{@lamb.id=>3}, {@goat.id=>3}]
+ end
+
+ it "creates orderproducts" do
+ expect(@order.orderproducts.length.must_equal 0)
+
+ Orderproduct.create_product_orders(@order.id, @session_cart)
+
+ Orderproduct.all.each do |orderproduct|
+ @order.orderproducts << orderproduct if orderproduct.order.id == @order.id
+ end
+
+ expect(@order.orderproducts.length.must_equal @session_cart.length)
+
+ @order.orderproducts.each do |orderproduct|
+ expect(orderproduct.order.id.must_equal @order.id)
+ expect(orderproduct.product.id.must_equal @goat.id || @lamb.id)
+ orderproduct.valid?.must_equal true
+ orderproduct.must_be_kind_of Orderproduct
+ end
+ end
+
+ it "doesnt create an orderproduct if there isn't enough stock" do
+ large_session_cart = [{@lamb.id=>100}]
+
+ expect {
+ Orderproduct.create_product_orders(@order.id, large_session_cart)
+ }.wont_change 'Orderproduct.all.length'
+ end
+ end
+
+end
diff --git a/test/models/product_test.rb b/test/models/product_test.rb
new file mode 100644
index 0000000000..d275a11204
--- /dev/null
+++ b/test/models/product_test.rb
@@ -0,0 +1,131 @@
+require "test_helper"
+
+describe Product do
+ describe "relationships do" do
+
+ it "has and belongs to many categories" do
+ product = products(:lamb)
+ category1 = categories(:mystical)
+ category2 = categories(:dangerous)
+ product.categories << category1
+ product.categories << category2
+
+ expect(product.categories.first).must_be_kind_of Category
+ expect(product.categories.length).must_equal 2
+ end
+
+ it "belongs to a user" do
+ @product = products(:lamb)
+
+ tan = @product.user
+
+ tan.must_be_kind_of User
+ end
+ end
+
+
+ describe "validations do" do
+
+ it "requires a name" do
+ user = users(:tan)
+ category = categories(:mystical)
+ @product = Product.new(price: 20000, user_id: user.id, category_id: category.id)
+ @product.valid?.must_equal false
+ @product.errors.messages.must_include :name
+ end
+
+ it "requires a price" do
+ user = users(:tan)
+ category = categories(:mystical)
+ @product = Product.new(name: "lamb", user_id: user.id, category_id: category.id)
+ @product.valid?.must_equal false
+ @product.errors.messages.must_include :price
+ end
+
+ it "adds product when price is a number" do
+ user = users(:tan)
+ category = categories(:mystical)
+ @product = Product.new(name: "warthog", price: 3000, user_id: user.id, category_id: category.id, stock_count:4, photo_url:"https://i.imgur.com/eI9VU0v.jpg")
+ @product.valid?.must_equal true
+ end
+
+ it "does not add a product when price is not a number" do
+ user = users(:tan)
+ category = categories(:mystical)
+
+ @product = Product.new(name: "lamb", price: "xyz", user_id: user.id, category_id: category.id)
+ @product.valid?.must_equal false
+ @product.errors.messages.must_include :price
+ end
+
+ it "requires a unique name do" do
+ @product1 = Product.new(name: "lamb", price: 2000)
+ @product1.save
+ @product2 = Product.new(name: "lamb", price: 3000)
+ @product2.save
+
+
+ @product2.valid?.must_equal false
+ end
+
+ it "adds a product when both name and price are present" do
+ user = users(:tan)
+ category = categories(:mystical)
+ @product = Product.new(name: "warthog", price: 3000, user_id: user.id, category_id: category.id, stock_count:4, photo_url:"https://i.imgur.com/eI9VU0v.jpg")
+ @product.valid?.must_equal true
+ end
+
+ it "has a list of reviews" do
+ @product = products(:lamb)
+ @product.must_respond_to :reviews
+
+ @product.reviews << reviews(:one)
+ @product.reviews.each do |review|
+ review.must_be_kind_of Review
+ end
+ end
+
+ end
+
+describe "custom methods" do
+
+ it "adjusts stock count according to products sold" do
+
+ product = products(:lamb)
+
+ Product.adjust_stock_count(product.id, 2)
+ product.reload
+ expect(product.stock_count).must_equal 8
+ end
+
+ it "gives average rating for review" do
+
+ product = products(:lamb)
+ rating = product.average_rating
+
+ expect(rating).must_equal 3
+ end
+
+ it "checks if product is in cart already" do
+ product = products(:lamb)
+ session = [{product.id=>3}]
+ cart = product.in_cart?(session)
+ expect(cart).must_equal true
+ end
+
+ it "checks if product is not in cart already" do
+ product = products(:lamb)
+ session = [{-1=>3}]
+ cart = product.in_cart?(session)
+ expect(cart).must_equal false
+ end
+
+ it "checks if cart quantity gets updated" do
+ product = products(:lamb)
+ session = [{product.id=>3}]
+ cart = product.cart_adjust_quantity(session)
+ expect(cart).must_equal 7
+ end
+
+end
+end
diff --git a/test/models/review_test.rb b/test/models/review_test.rb
new file mode 100644
index 0000000000..8bd44f8142
--- /dev/null
+++ b/test/models/review_test.rb
@@ -0,0 +1,51 @@
+require "test_helper"
+describe Review do
+ describe "relationships" do
+ before do
+ @review = reviews(:one)
+ @lamb = products(:lamb)
+ end
+
+ it "belongs to a product" do
+ @review.must_respond_to :product
+ @review.product.must_be_kind_of Product
+ end
+
+ it "has a product id" do
+ @review.product_id.must_equal @lamb.id
+ end
+ end
+
+ describe "validations" do
+ before do
+ @review = reviews(:one)
+ @goat = products(:goat)
+ end
+
+ it "requires a rating" do
+ review = Review.new(name: "Wendy", review: "Wonderful goat.", product_id: @goat.id)
+ review.valid?.must_equal false
+ end
+
+ it "requires a rating between 1 and 5" do
+ review = Review.new(name: "Wendy", rating: 100, review: "Wonderful goat.", product_id: @goat.id)
+ review.valid?.must_equal false
+ end
+
+ it "it's rating is a number" do
+ review = Review.new(name: "Wendy", rating: "hello", review: "Wonderful goat.", product_id: @goat.id)
+ review.valid?.must_equal false
+ end
+
+ it "requires a name" do
+ review = Review.new(rating: 3, review: "Wonderful goat.", product_id: @goat.id)
+ review.valid?.must_equal false
+ end
+
+ it "requires a review" do
+ review = Review.new(name: "Wendy", rating: 3, product_id: @goat.id)
+ review.valid?.must_equal false
+ end
+ end
+
+end
diff --git a/test/models/user_test.rb b/test/models/user_test.rb
new file mode 100644
index 0000000000..816b188e95
--- /dev/null
+++ b/test/models/user_test.rb
@@ -0,0 +1,94 @@
+require "test_helper"
+
+describe User do
+ describe "relations" do
+ let(:kit) { users(:kit) }
+ let(:tan) { users(:tan) }
+
+ it "has a list of products" do
+ kit.must_respond_to :products
+
+ kit.products.each do |product|
+ product.must_be_kind_of Product
+ end
+ end
+
+ it "has a list of orders" do
+ tan.must_respond_to :orders
+
+ tan.orders.each do |order|
+ order.must_be_kind_of Order
+ end
+ end
+
+ it "has a list of orderproducts" do
+ tan.must_respond_to :orderproducts
+
+ tan.orderproducts.each do |orderproduct|
+ orderproduct.must_be_kind_of Orderproduct
+ end
+ end
+ end
+
+ describe "validations" do
+ it "requires a name and email" do
+ user = User.new
+ user.valid?.must_equal false
+ user.errors.messages.must_include :name, :email
+ end
+
+ it "requires a unique user name and email" do
+ user1 = User.new(name: 'mat', email: 'mat@gmail.com', uid: 1234, provider: 'github')
+ user1.save!
+
+ user2 = User.new(name: 'mat', email: 'mat@gmail.com', uid: 1234, provider: 'github')
+ result = user2.save
+
+ result.must_equal false
+ user2.errors.messages.must_include :name, :email
+ end
+ end
+
+ describe 'custom methods' do
+
+ describe 'build_from_github(auth_hash)' do
+
+ it "builds a user from auth_hash" do
+ test_auth_hash = {
+ uid: 12345,
+ "info" => {
+ "email" => "joke@joke.com",
+ "name" => "Faker"
+ }
+ }
+ new_user = User.build_from_github(test_auth_hash)
+
+ expect(new_user.uid.must_equal test_auth_hash[:uid])
+ expect(new_user.email.must_equal test_auth_hash['info']['email'])
+ expect(new_user.name.must_equal test_auth_hash['info']['name'])
+ end
+
+ end
+
+ describe 'create_from_github(auth_hash)' do
+ it "saves a user from build_from_github method" do
+ test_auth_hash = {
+ uid: 12345,
+ "info" => {
+ "email" => "joke@joke.com",
+ "name" => "Faker"
+ }
+ }
+
+ expect {
+ new_user = User.create_from_github(test_auth_hash)
+ }.must_change 'User.count', 1
+
+ expect(User.last.uid.must_equal test_auth_hash[:uid])
+ expect(User.last.email.must_equal test_auth_hash['info']['email'])
+ expect(User.last.name.must_equal test_auth_hash['info']['name'])
+ end
+ end
+
+ 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..a39774466d
--- /dev/null
+++ b/test/test_helper.rb
@@ -0,0 +1,58 @@
+require 'simplecov'
+SimpleCov.start 'rails' do
+ add_filter '/bin/'
+ add_filter '/db/'
+ add_filter '/spec/' # for rspec
+ add_filter '/test/' # for minitest
+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
+ # Add more helper methods to be used by all tests here...
+ def setup
+ OmniAuth.config.test_mode = true
+ end
+
+ def perform_login(user)
+ stub_auth_hash!(mock_auth_hash(user))
+
+ get login_path('github')
+ end
+
+ def stub_auth_hash!(hash)
+ OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new(hash)
+ end
+
+ def mock_auth_hash(user)
+ return {
+ provider: user.provider,
+ uid: user.uid,
+ info: {
+ email: user.email,
+ name: user.name
+ }
+ }
+ 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