diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..fd226e08ce --- /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 + +# 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/* +.env 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..abaf41a078 --- /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.3' +# 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 + +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' +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' + gem 'dotenv-rails' +end + +group :test do + gem 'minitest-rails' + gem 'minitest-reporters' +end + +gem 'simplecov', require: false, group: :test + +gem 'csv' + +gem "omniauth" +gem "omniauth-github" \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000000..48f56a3dfc --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,317 @@ +GEM + remote: https://rubygems.org/ + specs: + actioncable (5.2.3) + actionpack (= 5.2.3) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + actionmailer (5.2.3) + actionpack (= 5.2.3) + actionview (= 5.2.3) + activejob (= 5.2.3) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 2.0) + actionpack (5.2.3) + actionview (= 5.2.3) + activesupport (= 5.2.3) + rack (~> 2.0) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (5.2.3) + activesupport (= 5.2.3) + builder (~> 3.1) + erubi (~> 1.4) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.3) + activejob (5.2.3) + activesupport (= 5.2.3) + globalid (>= 0.3.6) + activemodel (5.2.3) + activesupport (= 5.2.3) + activerecord (5.2.3) + activemodel (= 5.2.3) + activesupport (= 5.2.3) + arel (>= 9.0) + activestorage (5.2.3) + actionpack (= 5.2.3) + activerecord (= 5.2.3) + marcel (~> 0.3.1) + activesupport (5.2.3) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) + minitest (~> 5.1) + tzinfo (~> 1.1) + addressable (2.6.0) + public_suffix (>= 2.0.2, < 4.0) + ansi (1.5.0) + archive-zip (0.12.0) + io-like (~> 0.3.0) + arel (9.0.0) + autoprefixer-rails (9.5.1) + execjs + better_errors (2.5.1) + coderay (>= 1.0.0) + erubi (>= 1.0.0) + rack (>= 0.9.0) + bindex (0.7.0) + binding_of_caller (0.8.0) + debug_inspector (>= 0.0.1) + bootsnap (1.4.4) + 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 (11.0.1) + capybara (3.18.0) + addressable + mini_mime (>= 0.1.3) + nokogiri (~> 1.8) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + regexp_parser (~> 1.2) + xpath (~> 3.2) + childprocess (1.0.1) + rake (< 13.0) + chromedriver-helper (2.1.1) + archive-zip (~> 0.10) + nokogiri (~> 1.8) + coderay (1.1.2) + concurrent-ruby (1.1.5) + crass (1.0.4) + csv (3.1.1) + debug_inspector (0.0.3) + docile (1.3.1) + dotenv (2.7.2) + dotenv-rails (2.7.2) + dotenv (= 2.7.2) + railties (>= 3.2, < 6.1) + erubi (1.8.0) + execjs (2.7.0) + faraday (0.15.4) + multipart-post (>= 1.2, < 3) + ffi (1.10.0) + formatador (0.2.5) + globalid (0.4.2) + activesupport (>= 4.2.0) + guard (2.15.0) + 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.6.0) + i18n (1.6.0) + concurrent-ruby (~> 1.0) + io-like (0.3.0) + jbuilder (2.8.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.3) + 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.2) + mimemagic (0.3.3) + mini_mime (1.0.1) + mini_portile2 (2.4.0) + minitest (5.11.3) + minitest-rails (3.0.0) + minitest (~> 5.8) + railties (~> 5.0) + minitest-reporters (1.3.6) + ansi + builder + minitest (>= 5.0) + ruby-progressbar + msgpack (1.2.10) + 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.10.3) + mini_portile2 (~> 2.4.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.9.0) + hashie (>= 3.4.6, < 3.7.0) + rack (>= 1.6.2, < 3) + omniauth-github (1.3.0) + omniauth (~> 1.5) + omniauth-oauth2 (>= 1.4.0, < 2.0) + omniauth-oauth2 (1.6.0) + oauth2 (~> 1.1) + omniauth (~> 1.9) + pg (1.1.4) + popper_js (1.14.5) + pry (0.12.2) + coderay (~> 1.1.0) + method_source (~> 0.9.0) + pry-rails (0.3.9) + pry (>= 0.10.4) + public_suffix (3.0.3) + puma (3.12.1) + rack (2.0.7) + rack-test (1.1.0) + rack (>= 1.0, < 3) + rails (5.2.3) + actioncable (= 5.2.3) + actionmailer (= 5.2.3) + actionpack (= 5.2.3) + actionview (= 5.2.3) + activejob (= 5.2.3) + activemodel (= 5.2.3) + activerecord (= 5.2.3) + activestorage (= 5.2.3) + activesupport (= 5.2.3) + bundler (>= 1.3.0) + railties (= 5.2.3) + 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.3) + actionpack (= 5.2.3) + activesupport (= 5.2.3) + method_source + rake (>= 0.8.7) + thor (>= 0.19.0, < 2.0) + rake (12.3.2) + rb-fsevent (0.10.3) + rb-inotify (0.10.0) + ffi (~> 1.0) + regexp_parser (1.4.0) + ruby-progressbar (1.10.0) + ruby_dep (1.5.0) + rubyzip (1.2.2) + sass (3.7.4) + 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.142.0) + childprocess (>= 0.5, < 2.0) + 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.3) + thread_safe (0.3.6) + tilt (2.0.9) + turbolinks (5.2.0) + turbolinks-source (~> 5.2) + turbolinks-source (5.2.0) + tzinfo (1.2.5) + thread_safe (~> 0.1) + uglifier (4.1.20) + 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 + csv + 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.3) + 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.6 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/README.md b/README.md index 6db044f03d..8833ef16d2 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ +https://intangibly.herokuapp.com/ +https://git.heroku.com/intangibly.git +https://trello.com/b/A38BQTVC/intagibelles + # bEtsy ## At a Glance diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000000..e85f913914 --- /dev/null +++ b/Rakefile @@ -0,0 +1,6 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require_relative 'config/application' + +Rails.application.load_tasks diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js new file mode 100644 index 0000000000..b16e53d6d5 --- /dev/null +++ b/app/assets/config/manifest.js @@ -0,0 +1,3 @@ +//= link_tree ../images +//= link_directory ../javascripts .js +//= link_directory ../stylesheets .css diff --git a/app/assets/images/.keep b/app/assets/images/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/assets/images/favicon.png b/app/assets/images/favicon.png new file mode 100755 index 0000000000..ba3294c56c Binary files /dev/null and b/app/assets/images/favicon.png differ diff --git a/app/assets/images/intangible.png b/app/assets/images/intangible.png new file mode 100755 index 0000000000..9c1fa1a283 Binary files /dev/null and b/app/assets/images/intangible.png differ diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js new file mode 100644 index 0000000000..06eea96ab2 --- /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/homepages.js b/app/assets/javascripts/homepages.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/homepages.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/items.js b/app/assets/javascripts/items.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/items.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/merchants.js b/app/assets/javascripts/merchants.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/merchants.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/app/assets/javascripts/orders.js b/app/assets/javascripts/orders.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/orders.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/app/assets/javascripts/products.js b/app/assets/javascripts/products.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/products.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/app/assets/javascripts/reviews.js b/app/assets/javascripts/reviews.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/reviews.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss new file mode 100644 index 0000000000..04d97f45e0 --- /dev/null +++ b/app/assets/stylesheets/application.scss @@ -0,0 +1,124 @@ +/* + * 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 "**/*"; + +.nav-item { + color: rgba(131, 11, 142, 0.842); + } + + +.navbar-brand:hover{ + font-family: 'League Script', Helvetica, Arial, sans-serif; + margin-right: 11px; +} + +.navbar-brand { + margin-right: 20px; +} + +.nav_bar { + list-style: none; + display: flex; + flex-wrap: wrap; + justify-content: center; + padding: 20px 10px; + margin: 0; +} + + +.form-control:focus { + color: #495057; + background-color: #fff; + color: rgb(255, 157, 255); + border-color: rgb(255, 157, 255); + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(255, 157, 255, 0.25); +} + +.categories input { + margin-left: 20px; +} + +.btn-sm, .btn-group-sm > .btn { + margin-bottom: 0em; +} + +.btn-outline-success { + color: rgba(131, 11, 142, 0.842); + background-color: transparent; + background-image: none; + border-color: rgba(131, 11, 142, 0.842); +} + +.btn-outline-success:hover{ + background-color: rgba(131, 11, 142, 0.842); + border-color: rgba(131, 11, 142, 0.842); +} + +.btn-outline-success:not(:disabled):not(.disabled):active:focus, .btn-outline-success:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-success.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(131, 11, 142, 0.842); +} + +.btn-outline-success:not(:disabled):not(.disabled):active, .btn-outline-success:not(:disabled):not(.disabled).active, .show > .btn-outline-success.dropdown-toggle { + color: #fff; + background-color: rgba(131, 11, 142, 0.842); + border-color: rgba(131, 11, 142, 0.295); +} + +.btn-outline-success:focus, .btn-outline-success.focus { + box-shadow: 0 0 0 0.2rem rgba(131, 11, 142, 0.842); +} + + +#cart { + margin-bottom: 5px; + margin-right: 25px; + // margin-top: -5px; +} + +.dash-link { + margin-left: -15px; + margin-right: 13px; +} + +.log-in { + // margin-left: 15px; + margin-right: 10px; +} + +.log-out { + margin-left: 0px; + +} + +.sm-margin { + margin-top: 1em; +} + +.footer { + background-color: lavender; +} + +.sm-img{ + width: 25px; + border: none; + margin-bottom: 4px; +} + +main { + min-height: 73vh; +} \ No newline at end of file diff --git a/app/assets/stylesheets/categories.scss b/app/assets/stylesheets/categories.scss new file mode 100644 index 0000000000..42976cbc11 --- /dev/null +++ b/app/assets/stylesheets/categories.scss @@ -0,0 +1,3 @@ +// 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/ diff --git a/app/assets/stylesheets/homepages.scss b/app/assets/stylesheets/homepages.scss new file mode 100644 index 0000000000..9e97508988 --- /dev/null +++ b/app/assets/stylesheets/homepages.scss @@ -0,0 +1,219 @@ +// Place all the styles related to the Homepages controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ +body { + // background-image: url("https://picserio.com/data/out/166/winter-scenery-wallpaper-for-desktop_3829525.jpg"); + font-family: Arial, sans-serif; +} + +// possible new fonts: +// font-family: Verdana, Geneva +.centered { + text-align: center; +} + + +.bold { + font-weight: 900; + -webkit-text-stroke: #ff9dff 1px; +} + +.bold-grey { + -webkit-text-stroke: grey 1px; +} + +.pink { + color: #ff9dff; +} + +.geneva { + font-family: Geneva; +} + + +main { + margin-top: 2.0em; + margin-left: 2.5em; + margin-right: 2.5em; + margin-bottom: 5vw; +} + +h1 { + font-family: 'League Script', Arial, sans-serif; + font-weight:bold; + font-size: 10em; + color: rgb(198, 158, 255); + text-align: center; +} + +h2 { + font-family: 'League Script', Arial, sans-serif; + font-size: 70px; + color: rgb(198, 158, 255); + text-align: center; + padding-top: 1em; +} + +h3 { + font-family: 'Raleway Dots', Arial, sans-serif; + font-weight:bold; + font-size: 2.4em; + margin-top: 1em; + color: rgb(255, 157, 255); +} + +h4 { + font-family: 'Raleway Dots', Arial, sans-serif; + font-weight:bold; + font-size: 30px; + color:grey; +} + +h4 a:hover { + font-family: 'Raleway Dots', Arial, sans-serif; + font-weight:bold; + font-size: 30px; + color: rgb(255, 157, 255); + -webkit-text-stroke: rgb(255, 157, 255) 1px; +} + +.bg-purple { + background-color: lavender; +} +li { + font-family: Arial, sans-serif, 'Arial Narrow Bold', sans-serif; +} + +ul { + font-family: 'Courier New', Courier, monospace; + list-style-type: none; +} + +a { + color: #333; +} + +a:hover { + color: #000; + text-decoration: none; +} + +.whitebg { + background-color: #fff; +} + +#background-image-magical { + background-color: transparent; +} + +.centered_grey_text { + text-align: center; + font-size: 2em; + font-family: 'Raleway Dots', Arial, sans-serif; + color:rgb(87, 87, 87); +} + +#mainpadding { + padding-top: 2em; +} + +#tagline { + font-family: 'Raleway Dots', Arial, sans-serif; + font-size: 4em; + color: rgba(131, 11, 142, 0.842); + text-align: center; + margin: 10px; +} + + +.nav-link { + color: rgba(0, 0, 0, 0.5); +} + +.nav_bar_header ul li { + padding: 20px 30px; + display: block; +} + +.page-btn { + padding: 5em; +} + +.centered-arial { + font-family: Arial, sans-serif; + + color:rgb(87, 87, 87); + font-size: 1.3em; + margin: 3em; + text-align: center; +} + +.jumbotron { + display: grid; + grid-template: 3fr / 1fr 1fr; +} + +.jumbotron h3 { + grid-column: 1 / 3; +} + +// #footer { +// position: absolute; +// bottom: 0; +// width: 100%; +// height: 2.5rem; +// } + +.img { + border: none; + margin-top: -10px; +} + + +/* ---------------------------------------------- + * Generated by Animista on 2019-5-10 1:57:28 + * w: http://animista.net, t: @cssanimista + * ---------------------------------------------- */ + +/** + * ---------------------------------------- + * animation roll-in-blurred-left + * ---------------------------------------- + */ + @-webkit-keyframes roll-in-blurred-left { + 0% { + -webkit-transform: translateX(-1000px) rotate(-720deg); + transform: translateX(-1000px) rotate(-720deg); + -webkit-filter: blur(50px); + filter: blur(50px); + opacity: 0; + } + 100% { + -webkit-transform: translateX(0) rotate(0deg); + transform: translateX(0) rotate(0deg); + -webkit-filter: blur(0); + filter: blur(0); + opacity: 1; + } +} +@keyframes roll-in-blurred-left { + 0% { + -webkit-transform: translateX(-1000px) rotate(-720deg); + transform: translateX(-1000px) rotate(-720deg); + -webkit-filter: blur(50px); + filter: blur(50px); + opacity: 0; + } + 100% { + -webkit-transform: translateX(0) rotate(0deg); + transform: translateX(0) rotate(0deg); + -webkit-filter: blur(0); + filter: blur(0); + opacity: 1; + } +} + +.roll-in-blurred-left { + -webkit-animation: roll-in-blurred-left 0.65s cubic-bezier(0.230, 1.000, 0.320, 1.000) both; + animation: roll-in-blurred-left 0.65s cubic-bezier(0.230, 1.000, 0.320, 1.000) both; +} \ No newline at end of file diff --git a/app/assets/stylesheets/items.scss b/app/assets/stylesheets/items.scss new file mode 100644 index 0000000000..a9b4bb9d1a --- /dev/null +++ b/app/assets/stylesheets/items.scss @@ -0,0 +1,42 @@ +// Place all the styles related to the Items controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ + +.jumbotron li { + margin-left: 0px; + +} +.jumbotron h3 { + grid-column: 1 / 3; + margin-bottom: 0; + margin-top: 0; + } + +// .jumbotron img { +// grid-row: 2 / 4; +// } + +.jumbotron p { + margin-bottom: 0; + grid-column: 1 / 3; +} + +.col-md-4 { + padding: 0px; +} + +.inner-flex { + display: flex; + flex-wrap: wrap; + flex-direction: row; + justify-content: flex-start; + align-items: flex-start; +} + +.inner-flex div { + margin-right: 2em; +} + +.btn-sm { + margin-bottom: 1.3em; +} \ No newline at end of file diff --git a/app/assets/stylesheets/merchants.scss b/app/assets/stylesheets/merchants.scss new file mode 100644 index 0000000000..0f62e62bf3 --- /dev/null +++ b/app/assets/stylesheets/merchants.scss @@ -0,0 +1,7 @@ +// Place all the styles related to the Merchants controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ + +.merchant-dashboard td a:hover { + color: rgb(255, 157, 255); +} \ No newline at end of file diff --git a/app/assets/stylesheets/orders.scss b/app/assets/stylesheets/orders.scss new file mode 100644 index 0000000000..e2d9820a63 --- /dev/null +++ b/app/assets/stylesheets/orders.scss @@ -0,0 +1,18 @@ +// 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/ + +.item.card { + width: 300px; + +} + +.flex { + display: flex; + flex-flow: row wrap; + justify-content: space-around; +} + +.checkout { + margin-top: 2em; +} \ No newline at end of file diff --git a/app/assets/stylesheets/products.scss b/app/assets/stylesheets/products.scss new file mode 100644 index 0000000000..9ee9950608 --- /dev/null +++ b/app/assets/stylesheets/products.scss @@ -0,0 +1,57 @@ +// 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/ + +img { + border-bottom: .1em #333333 solid; + max-width: 200px; + max-height: 200px; + } + + .flex-container { + display: flex; + flex-wrap: wrap; + flex-direction: row; + justify-content: space-around; + align-items: flex-start; + margin-top: 1.5em; + margin-left: 0; + } + + + +.flex-item-products { + background-color: #e6e6fa; + border: #333333 .1em solid; + text-align: center; + margin-bottom: 1em; + margin-left: .4em; + margin-right: .4em; + } + + .flex-item-products p { + margin: .3em; + } + + img.detail { + max-width: 40vw; + max-height: 300px; + } + +h4 { + margin-top: 1em; +} + +.strong { + font-weight: bold; +} + +.categories input { +// margin-left: 20px; + margin-right: .4em; + margin-bottom: 1.5em; +} + +.form-check.categories { + margin-left: 0%; +} diff --git a/app/assets/stylesheets/reviews.scss b/app/assets/stylesheets/reviews.scss new file mode 100644 index 0000000000..11bbb12cd5 --- /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/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..1ac9dd4dc7 --- /dev/null +++ b/app/controllers/application_controller.rb @@ -0,0 +1,13 @@ +class ApplicationController < ActionController::Base + private + + def find_logged_in_merchant + if session[:merchant_id] + @login_merchant = Merchant.find_by(id: session[:merchant_id]) + end + end + + def find_cart_order + @order = Order.find_by(id: session[:cart_id]) + end +end diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb new file mode 100644 index 0000000000..025a424249 --- /dev/null +++ b/app/controllers/categories_controller.rb @@ -0,0 +1,51 @@ +class CategoriesController < ApplicationController + before_action :find_logged_in_merchant, only: [:new, :create] + + def new + if @login_merchant + @category = Category.new + else + flash[:error] = "You must be logged in to add a new category" + redirect_to root_path + end + end + + def create + if @login_merchant + @category = Category.new(category_params) + is_successful = @category.save + + if is_successful + flash[:success] = "Category added successfully" + redirect_to categories_path + else + flash.now[:error] = "Could not add new category:" + @category.errors.messages.each do |label, message| + flash.now[label.to_sym] = message + end + render :new, status: :bad_request + end + else + flash.now[:error] = "You must be logged in to create a new category" + render :new, status: :bad_request + end + end + + def index + @categories = Category.all + end + + def show + @category = Category.find_by(id: params[:id]) + if @category.nil? + flash[:error] = "Unknown category" + redirect_to categories_path + end + end + + private + + def category_params + return params.require(:category).permit(:name) + 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/homepages_controller.rb b/app/controllers/homepages_controller.rb new file mode 100644 index 0000000000..f200c8ac2e --- /dev/null +++ b/app/controllers/homepages_controller.rb @@ -0,0 +1,4 @@ +class HomepagesController < ApplicationController + def index + end +end diff --git a/app/controllers/items_controller.rb b/app/controllers/items_controller.rb new file mode 100644 index 0000000000..f1d1bba360 --- /dev/null +++ b/app/controllers/items_controller.rb @@ -0,0 +1,75 @@ +class ItemsController < ApplicationController + before_action :find_cart_order, only: [:create] + + def create + # find product and order + @product = Product.find_by(id: params[:product_id]) + # if no current cart, create order and save to session. + unless @order + @order = Order.create + session[:cart_id] = @order.id + end + # finds item in cart if already present and increments quantity (required for easy checkout) + item = @order.items.find_by(product_id: @product.id) if @product + if item + item.quantity += params[:item][:quantity].to_i + else + item = Item.new(item_params) + end + + if !@product + flash.now[:error] = "Could Not Add To Cart: product not available" + redirect_to products_path + + # check if requested quantity is available + elsif item.quantity && @product.stock < item.quantity + flash.now[:error] = "Could Not Add To Cart: quantity selected is more than available stock" + render "products/show", status: :bad_request + else + + # set up relationships for item + @product.items << item + @order.items << item + + if item.valid? + flash[:success] = "#{@product.name} (total quantity: #{item.quantity}) Successfully Added To Cart" + redirect_to product_path(@product.id) + else + flash.now[:error] = "Could Not Add To Cart" + item.errors.messages.each do |label, message| + flash.now[label.to_sym] = message + end + render "products/show", status: :bad_request + end + end + end + + def update + item = Item.find_by(id: params[:id]) + if item && item.update(item_params) && item.available_for_purchase? + flash[:success] = "#{item.product.name} successfully updated" + redirect_to cart_path + else + flash[:error] = "Unable to update item" + redirect_to cart_path + end + end + + def destroy + item = Item.find_by(id: params[:id]) + if item + flash[:success] = "#{item.product.name} successfully removed from cart" + item.destroy + redirect_to cart_path + else + flash[:error] = "Unable to remove item" + redirect_to cart_path + end + end + + private + + def item_params + return params.require(:item).permit(:quantity) + end +end diff --git a/app/controllers/merchants_controller.rb b/app/controllers/merchants_controller.rb new file mode 100644 index 0000000000..95bd0a096f --- /dev/null +++ b/app/controllers/merchants_controller.rb @@ -0,0 +1,48 @@ +class MerchantsController < ApplicationController + before_action :find_logged_in_merchant, only: [:current] + + def index + @merchants = Merchant.all + end + + def show + @merchant = Merchant.find_by(id: params[:id]) + + if @merchant.nil? + flash[:error] = "Unknown merchant" + redirect_to merchants_path + end + end + + def create + auth_hash = request.env["omniauth.auth"] + merchant = Merchant.find_by(uid: auth_hash[:uid], provider: "github") + if merchant + flash[:success] = "Logged in as returning merchant #{merchant.username}" + else + merchant = Merchant.build_from_github(auth_hash) + + if merchant.save + flash[:success] = "Logged in as new merchant #{merchant.username}" + else + flash[:error] = "Could not create new merchant account: #{merchant.errors.messages}" + return redirect_to root_path + end + end + session[:merchant_id] = merchant.id + return redirect_to root_path + end + + def logout + session[:merchant_id] = nil + flash[:success] = "Successfully logged out." + redirect_to root_path + end + + def current + if @login_merchant.nil? + flash[:error] = "You must be logged to view this page" + redirect_to root_path + end + end +end diff --git a/app/controllers/orders_controller.rb b/app/controllers/orders_controller.rb new file mode 100644 index 0000000000..007d3fce5e --- /dev/null +++ b/app/controllers/orders_controller.rb @@ -0,0 +1,77 @@ +class OrdersController < ApplicationController + before_action :find_cart_order, except: [:show, :index] + before_action :find_logged_in_merchant, only: [:show, :update] + + def show + if @login_merchant + @order = Order.find_by(id: params[:id]) + if !@order || !@order.items.any? { |item| item.product.merchant_id == @login_merchant.id } + flash[:error] = "Can not view order page. No items sold by merchant." + redirect_to current_merchant_path + end + else + flash[:error] = "You must be logged to view this page" + redirect_to root_path + end + end + + def update + @order = Order.find_by(id: params[:id]) + if !@login_merchant || !@order || !@order.items.any? { |item| item.product.merchant_id == @login_merchant.id } || !@order.update(order_params) + flash[:error] = "Can not update order" + redirect_to root_path + else + flash[:success] = "Order updated" + redirect_to current_merchant_path + end + end + + def view_cart + end + + def confirmation + @order = Order.find_by(id: params[:id]) + if !@order || session[:confirmation] != @order.id + flash[:error] = "Checkout cart to view confirmation page" + redirect_to root_path + else + session[:confirmation] = nil + end + end + + def checkout + unless @order + flash[:error] = "Unable to checkout cart" + redirect_to cart_path + end + end + + def purchase + if @order && @order.update(order_params) && @order.cart_errors.empty? && @order.update(status: "paid") + @order.cart_checkout + session[:confirmation] = session[:cart_id] + session[:cart_id] = nil + flash[:success] = "Purchase Successful" + redirect_to order_confirmation_path(@order.id) + else + flash[:error] = "Unable to checkout cart" + if @order.cart_errors.any? + cart_errors = [] + @order.cart_errors.each do |item| + cart_errors << "#{item.product.name} available stock is #{item.product.stock}." + end + flash["Item exceeds available stock:"] = cart_errors + end + redirect_to cart_path + end + end + + private + + def order_params + if params[:order][:cc_all] + params[:order][:cc_four] = params[:order][:cc_all].length <= 4 ? params[:order][:cc_all] : params[:order][:cc_all][-4..-1] + end + params.require(:order).permit(:shopper_name, :shopper_email, :shopper_address, :cc_exp, :cc_four, :status) + end +end diff --git a/app/controllers/products_controller.rb b/app/controllers/products_controller.rb new file mode 100644 index 0000000000..2def075efc --- /dev/null +++ b/app/controllers/products_controller.rb @@ -0,0 +1,103 @@ +class ProductsController < ApplicationController + before_action :find_logged_in_merchant, except: [:index, :show] + before_action :find_product, except: [:new, :create, :index] + + def new + if @login_merchant + @product = Product.new(photo_url: "https://i.ibb.co/6r2mYnD/wintery.jpg") + else + flash[:error] = "You must be logged in to add a new product" + redirect_to root_path + end + end + + def create + if @login_merchant + @product = Product.new(product_params) + @product.merchant_id = session[:merchant_id] + @product.save + + if @product.valid? + flash[:success] = "Product added successfully" + redirect_to product_path(@product.id) + else + flash.now[:error] = "Could not add new product:" + @product.errors.messages.each do |label, message| + flash.now[label.to_sym] = message + end + render :new, status: :bad_request + end + else + flash.now[:error] = "You must be logged in to create a new product" + render :new, status: :bad_request + end + end + + def index + @products = Product.all + end + + def show + if @product.nil? || !@product.active + flash[:error] = "Unknown product" + redirect_to products_path + end + end + + def edit + if @login_merchant + if @product.nil? || !@product.active + flash[:error] = "Unknown product" + redirect_to products_path + end + else + flash[:error] = "You must be logged in to edit a product" + redirect_to products_path + end + end + + def update + if @product.merchant_id == @login_merchant.id + @product.update(product_params) + + if @product.valid? + flash[:success] = "Product updated successfully" + redirect_to product_path(@product.id) + else + @product.errors.messages.each do |label, message| + flash.now[label.to_sym] = message + end + render :edit, status: :bad_request + end + else + flash[:error] = "You can only update your own products" + redirect_to products_path + end + end + + def toggle_inactive + if @login_merchant && @product.valid? + if @product.merchant_id == @login_merchant.id && @product.toggle(:active).save + flash[:success] = "Product status changed successfully" + else + flash[:error] = "You may only change the status of your own products" + end + else + flash[:error] = "You must be logged in to change the status of a product" + end + redirect_to current_merchant_path + end + + private + + def find_product + @product = Product.find_by(id: params[:id]) + end + + def product_params + if params[:product][:price] + params[:product][:price] = params[:product][:price].to_f * 100.0 + end + return params.require(:product).permit(:name, :price, :description, :photo_url, :stock, :merchant_id, category_ids: []) + end +end diff --git a/app/controllers/reviews_controller.rb b/app/controllers/reviews_controller.rb new file mode 100644 index 0000000000..41950e63e5 --- /dev/null +++ b/app/controllers/reviews_controller.rb @@ -0,0 +1,28 @@ +class ReviewsController < ApplicationController + def create + @product = Product.find(params[:product_id]) + if !@product || @product.merchant_id == session[:merchant_id] + flash.now[:error] = "Merchants cannot review their own product." + render "products/show", status: :bad_request + else + review = Review.new(review_params) + @product.reviews << review + if review.valid? + flash[:success] = "Review Successfully Added" + redirect_to product_path(@product.id) + else + flash.now[:error] = "Could Not Add Review" + review.errors.messages.each do |label, message| + flash.now[label.to_sym] = message + end + render "products/show", status: :bad_request + end + end + end + + private + + def review_params + return params.require(:review).permit(:comment, :rating) + end +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb new file mode 100644 index 0000000000..de6be7945c --- /dev/null +++ b/app/helpers/application_helper.rb @@ -0,0 +1,2 @@ +module ApplicationHelper +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/homepages_helper.rb b/app/helpers/homepages_helper.rb new file mode 100644 index 0000000000..4bd8098f37 --- /dev/null +++ b/app/helpers/homepages_helper.rb @@ -0,0 +1,2 @@ +module HomepagesHelper +end diff --git a/app/helpers/items_helper.rb b/app/helpers/items_helper.rb new file mode 100644 index 0000000000..cff0c9fe21 --- /dev/null +++ b/app/helpers/items_helper.rb @@ -0,0 +1,2 @@ +module ItemsHelper +end diff --git a/app/helpers/merchants_helper.rb b/app/helpers/merchants_helper.rb new file mode 100644 index 0000000000..5337747b0f --- /dev/null +++ b/app/helpers/merchants_helper.rb @@ -0,0 +1,2 @@ +module MerchantsHelper +end diff --git a/app/helpers/orders_helper.rb b/app/helpers/orders_helper.rb new file mode 100644 index 0000000000..443227fd48 --- /dev/null +++ b/app/helpers/orders_helper.rb @@ -0,0 +1,2 @@ +module OrdersHelper +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/jobs/application_job.rb b/app/jobs/application_job.rb new file mode 100644 index 0000000000..a009ace51c --- /dev/null +++ b/app/jobs/application_job.rb @@ -0,0 +1,2 @@ +class ApplicationJob < ActiveJob::Base +end diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb new file mode 100644 index 0000000000..286b2239d1 --- /dev/null +++ b/app/mailers/application_mailer.rb @@ -0,0 +1,4 @@ +class ApplicationMailer < ActionMailer::Base + default from: 'from@example.com' + layout 'mailer' +end diff --git a/app/models/application_record.rb b/app/models/application_record.rb new file mode 100644 index 0000000000..10a4cba84d --- /dev/null +++ b/app/models/application_record.rb @@ -0,0 +1,3 @@ +class ApplicationRecord < ActiveRecord::Base + self.abstract_class = true +end diff --git a/app/models/category.rb b/app/models/category.rb new file mode 100644 index 0000000000..f8fdb0c9c1 --- /dev/null +++ b/app/models/category.rb @@ -0,0 +1,5 @@ +class Category < ApplicationRecord + has_and_belongs_to_many :products + + validates :name, presence: true, uniqueness: true +end diff --git a/app/models/concerns/.keep b/app/models/concerns/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/models/item.rb b/app/models/item.rb new file mode 100644 index 0000000000..e5be7e4c1f --- /dev/null +++ b/app/models/item.rb @@ -0,0 +1,24 @@ +class Item < ApplicationRecord + belongs_to :order + belongs_to :product + + validates :quantity, presence: true + validates_numericality_of :quantity, greater_than: 0 + + def subtotal + return unless self.valid? && self.product.valid? + return quantity * product.price / 100.0 + end + + # checks if item is are available w/ quantity selecged when checking out incase another person + # has checked out product since current item was placed in cart. + def available_for_purchase? + return unless self.valid? && self.product.valid? + return self.quantity <= product.stock + end + + def purchase + return unless self.valid? + return product.decrease_stock(self.quantity) + end +end diff --git a/app/models/merchant.rb b/app/models/merchant.rb new file mode 100644 index 0000000000..140747b17e --- /dev/null +++ b/app/models/merchant.rb @@ -0,0 +1,17 @@ +class Merchant < ApplicationRecord + has_many :products + + validates :username, presence: true, uniqueness: true + validates :email, presence: true, uniqueness: true + validates :uid, presence: true + validates :provider, presence: true + + def self.build_from_github(auth_hash) + merchant = Merchant.new + merchant.username = auth_hash[:info][:nickname] + merchant.email = auth_hash[:info][:email] + merchant.uid = auth_hash[:uid] + merchant.provider = "github" + return merchant + end +end diff --git a/app/models/order.rb b/app/models/order.rb new file mode 100644 index 0000000000..532dbcf4bc --- /dev/null +++ b/app/models/order.rb @@ -0,0 +1,72 @@ +class Order < ApplicationRecord + has_many :items + validates_inclusion_of :status, :in => ["pending", "paid", "complete", "cancelled"] + + def total + return items.map do |item| + return unless item.valid? && item.product.valid? + item.subtotal + end.sum + end + + def cart_errors + cart_errors = [] + items.each do |item| + return unless item.valid? && item.product.valid? + unless item.available_for_purchase? + cart_errors << item + end + end + return cart_errors + end + + def cart_checkout + items.each do |item| + return unless item.valid? && item.product.valid? + item.purchase + end + return true + end + + # stores merchant order items in hash: + # key is order status and value is a + # hash with order_id as key and array of items as value: + # items_hash { + # "paid" => {"11": [item1, item2, item4]}, "16": [item7, item8, item14]}, + # "complete" => {"15": [item11, item12, item17]} + # } + # to add more statuses to track follow items_hash template -> { "paid" => {}, "complete" => {} } + def self.find_merchant_order_items(merchant, items_hash: { "paid" => {}, "complete" => {} }) + Order.all.each do |order| + order.items.each do |item| + if item && items_hash.include?(order.status) && item.product.merchant_id == merchant.id + if !items_hash[order.status.to_s].include?(order.id.to_s) + items_hash[order.status.to_s][order.id.to_s] = [item] + else + items_hash[order.status.to_s][order.id.to_s] << item + end + end + end + end + return items_hash + end + + # takes input of items_hash["status"] where status is a status in items_hash + def self.status_revenue(item_status) + revenue = 0 + return 0 if item_status == nil + item_status.each do |order_id, items| + sum = items.sum do |item| + item.subtotal + end + revenue += sum + end + return revenue + end + + # takes input of items_hash["status"] where status is a status in items_hash + def self.status_count_orders(item_status) + return 0 if !item_status + return item_status.count + end +end diff --git a/app/models/product.rb b/app/models/product.rb new file mode 100644 index 0000000000..df4ae0c54b --- /dev/null +++ b/app/models/product.rb @@ -0,0 +1,18 @@ +class Product < ApplicationRecord + belongs_to :merchant + has_and_belongs_to_many :categories + has_many :reviews + has_many :items + + validates :name, presence: true, uniqueness: true + validates :price, presence: true + validates :stock, presence: true + validates_numericality_of :price, greater_than: 0 + validates_numericality_of :stock, greater_than: -1 + + def decrease_stock(quantity) + return unless self.valid? + self.stock -= quantity + return self.save + end +end diff --git a/app/models/review.rb b/app/models/review.rb new file mode 100644 index 0000000000..f25b33fbff --- /dev/null +++ b/app/models/review.rb @@ -0,0 +1,6 @@ +class Review < ApplicationRecord + belongs_to :product + + validates :rating, presence: true + validates_inclusion_of :rating, :in => [1, 2, 3, 4, 5] +end diff --git a/app/views/categories/index.html.erb b/app/views/categories/index.html.erb new file mode 100644 index 0000000000..5a96857ab7 --- /dev/null +++ b/app/views/categories/index.html.erb @@ -0,0 +1,6 @@ +

Categories

+ +<% @categories.each do |category| %> + +

<%= link_to category.name, category_path(category.id) %>

+<% end %> diff --git a/app/views/categories/new.html.erb b/app/views/categories/new.html.erb new file mode 100644 index 0000000000..e09846a7fb --- /dev/null +++ b/app/views/categories/new.html.erb @@ -0,0 +1,12 @@ +

Form for creating a new category

+<%= form_with model: @category, class: "new-category" do |f| %> +
+
+ <%=f.label "Category name"%> + <%=f.text_field :name, required: true, class: "form-control"%> +
+
+
+ <%=f.submit "Create Category", class:"btn btn-primary"%> +
+ <% end %> \ No newline at end of file diff --git a/app/views/categories/show.html.erb b/app/views/categories/show.html.erb new file mode 100644 index 0000000000..70bb349b43 --- /dev/null +++ b/app/views/categories/show.html.erb @@ -0,0 +1,29 @@ +
+

Shop <%= @category.name %>

+ + <% if @category.product_ids.any? %> +
+ + <% @category.products.each do |product| %> + + <% if product.active %> +
+ <%= link_to image_tag(product.photo_url), product_path(product.id) %> +

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

+ +

Price: $ <%= "%.2f" % (product.price / 100.0) %>

+
+ <% end %> + <% end %> + + +
+ <% else %> +

There are no products for this category

+ <% end %> +
+ +
+ <%= link_to "Back to all products", products_path, class: "btn btn-outline-success btn-sm" %> + <%= link_to "Back to all categories", categories_path, class: "btn btn-outline-success btn-sm" %> +
\ No newline at end of file diff --git a/app/views/homepages/index.html.erb b/app/views/homepages/index.html.erb new file mode 100644 index 0000000000..bfed20db32 --- /dev/null +++ b/app/views/homepages/index.html.erb @@ -0,0 +1,12 @@ +
+ +

Intangibly

+

<%= image_tag "intangible.png", alt: "logo", class: "img" %>find your magic

+ <% if session[:merchant_id] %> + <% merchant = Merchant.find_by(id: session[:merchant_id]) %> +

<%= "You are currently logged in as #{merchant.username}" %>

+ <%# wrap in class for footer %> + <% end %> +
+ + diff --git a/app/views/items/_item_form.html.erb b/app/views/items/_item_form.html.erb new file mode 100644 index 0000000000..e2c5c12ac5 --- /dev/null +++ b/app/views/items/_item_form.html.erb @@ -0,0 +1,15 @@ + +<%=form_with model: item , url: url_path, method: method_verb do |f| %> + +
+ <%= f.label :Quantity%> + <% if @product.stock > 0%> + <%= f.number_field :quantity, required: true, in: 1..@product.stock %> + <% else %> + <%= f.number_field :quantity, required: true, in: 0..1 %> + <%end%> +
+
+ <%= f.submit action, class: "btn btn-info sm-margin" %> +
+<%end%> \ No newline at end of file diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb new file mode 100644 index 0000000000..9651402eff --- /dev/null +++ b/app/views/layouts/application.html.erb @@ -0,0 +1,95 @@ + + + + + Intangibly + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + + + + + <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> + <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> + <%= favicon_link_tag asset_path('intangible.png') %> + + +
+ +
+ <% if flash[:success] %> +
+ <%= flash[:success] %> +
+ <% elsif flash[:error] %> +
+ <%= flash[:error] %> +
    + <%flash.each do |label, problems|%> + <% if label != "error" %> + <% problems.each do |problem| %> +
  • <%= label %>: <%= problem %>
  • + <% end %> + <% end %> + <% 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/merchants/current.html.erb b/app/views/merchants/current.html.erb new file mode 100644 index 0000000000..42bb62f610 --- /dev/null +++ b/app/views/merchants/current.html.erb @@ -0,0 +1,110 @@ +
+

Merchant Dashboard

+
+

Welcome <%= @login_merchant.username %>

+ <%= link_to "Add new product", new_product_path, class: "btn btn-outline-info" %> + <%= link_to "Add new category", new_category_path, class: "btn btn-outline-info" %> +
+ +<% item_hash = Order.find_merchant_order_items(@login_merchant) %> + +

✨Merchant Metrics

+ + + + + + + + + + + + + + + + + + + + + + + + + +
StatusNumber of OrdersRevenue
Paid<%= item_hash["paid"].count %>$<%= "%.2f" % Order.status_revenue(item_hash["paid"])%>
Complete<%= item_hash["complete"].count %>$<%= "%.2f" % Order.status_revenue(item_hash["complete"])%>
Total<%= item_hash["complete"].count + item_hash["paid"].count%>$<%= "%.2f" % (Order.status_revenue(item_hash["paid"]) + Order.status_revenue(item_hash["complete"]))%>
+ +

✨Orders by status

+<%["paid", "complete"].each do |status|%> +

<%=status.capitalize%> Orders

+ <% item_hash[status].each do |order, items| %> + + + + + + + + <% if status == "paid"%> + + <%else%> + + <%end%> + + + + + + + + + + + + + <%items.each do |item| %> + + + + + + + + <%end%> + +
<%= link_to "Order ID: #{Order.find(order).id}", order_path(Order.find(order).id), class: "btn btn-outline-success btn-sm" %> Date & Time of order: <%= Order.find(order).created_at.strftime("%B %d, %T")%>Order Status: <%= Order.find(order).status %> <%=link_to "SHIP ORDER", order_path(order.to_i, {order: {status: "complete"}}), method: :patch, class: "btn btn-sm btn-info"%>
QuantityProduct NameUnit PriceSubtotal
<%=item.quantity%><%=link_to item.product.name.upcase, product_path(item.product.id) %>$<%= "%.2f" % (item.product.price / 100.0)%>$<%="%.2f" % (item.subtotal) %>
+ <%end%> +<%end%> + +<% @products = Product.where(merchant_id: @login_merchant.id).order(:active).reverse %> +

✨All Products

+ + + + + + + + + + + + + + <% @products.each do |product|%> + + + + + + + + + <%end%> + +
Item IDNamePriceDescriptionActiveRetire ProductEdit Product
<%=product.id%><%=link_to product.name.upcase, product_path(product.id) %>$<%= "%.2f" % (product.price / 100.0)%><%=product.description %><%=product.active? ? "Active" : "Retired" %> + <%=link_to "Retire product", toggle_inactive_path(product.id), method: :patch, class: "btn btn-outline-success btn-sm" if product.active?%> + <%=link_to "Activate product", toggle_inactive_path(product.id), method: :patch, class: "btn btn-outline-success btn-sm" if product.active? == false%><%=link_to "Edit product", edit_product_path(product.id), class: "btn btn-outline-success btn-sm" if product.active?%>
+
\ No newline at end of file diff --git a/app/views/merchants/index.html.erb b/app/views/merchants/index.html.erb new file mode 100644 index 0000000000..36d6a2e7f9 --- /dev/null +++ b/app/views/merchants/index.html.erb @@ -0,0 +1,7 @@ +

Merchants

+ +<% @merchants.each do |merchant| %> + +

<%= link_to merchant.username, merchant_path(merchant.id) %>

+ +<% end %> diff --git a/app/views/merchants/show.html.erb b/app/views/merchants/show.html.erb new file mode 100644 index 0000000000..e1ac9a52f3 --- /dev/null +++ b/app/views/merchants/show.html.erb @@ -0,0 +1,28 @@ +
+

Shop <%= @merchant.username %>

+ + <% if @merchant.product_ids.any? %> +
+ + <% @merchant.products.each do |product| %> + + <% if product.active %> +
+ <%= link_to image_tag(product.photo_url), product_path(product.id) %> +

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

+ +

Price: $ <%= "%.2f" % (product.price / 100.0) %>

+
+ <% end %> + <% end %> + +
+ <% else %> +

There are no products for this merchant

+ <% end %> +
+ +
+ <%= link_to "Back to all products", products_path, class: "btn btn-outline-success btn-sm" %> + <%= link_to "Back to all merchants", merchants_path, class: "btn btn-outline-success btn-sm" %> +
\ No newline at end of file diff --git a/app/views/orders/_order_form.html.erb b/app/views/orders/_order_form.html.erb new file mode 100644 index 0000000000..7dce301f24 --- /dev/null +++ b/app/views/orders/_order_form.html.erb @@ -0,0 +1,68 @@ +

Cart Check Out

+

Order Items:

+ + + + + + + + + + + + <%@order.items.each_with_index do |item| %> + + + + + + + <%end%> + + + + <%# REMOVE h3, temp added to get a feel for style%> + + + +
QuantityProductUnit PriceSubtotal
<%=item.quantity%><%=link_to item.product.name, product_path(item.product.id) %>$<%=item.product.price / 100.0%>$<%=item.subtotal %>

Total:

$<%="%0.2f" % [@order.total]%>

+ +<%= form_with model: @order, url: cart_path(), method: :patch do |f|%> +
+
+ <%=f.label "Name on Credit Card"%> + <%=f.text_field :shopper_name, required: true, class: "form-control"%> +
+
+ <%=f.label "Email Address" %> + <%=f.email_field :shopper_email, required: true, class: "form-control" %> +
+
+
+ <%=f.label "Mailing Address"%> + <%=f.text_field :shopper_address, required: true, class: "form-control"%> +
+
+ <%=f.label "Credit Card Number" %> + <%=f.text_field :cc_all, required: true, minlength: 4 , class: "form-control"%> +
+
+
+ <%=f.label "Credit cart expiration"%> + <%=f.date_field :cc_exp, required: true, class: "form-control"%> +
+
+ <%=f.label "Credit Card CVV (security code)"%> + <%=f.text_field :cvv , required: true, class: "form-control"%> +
+
+ <%=f.label "Billing zip code"%> + <%=f.text_field :zip, required: true, class: "form-control"%> +
+
+
+ <%=f.submit "Purchase", class:"btn btn-outline-info sm-margin btn-block"%> +
+<%end%> + diff --git a/app/views/orders/checkout.html.erb b/app/views/orders/checkout.html.erb new file mode 100644 index 0000000000..ada0b4d646 --- /dev/null +++ b/app/views/orders/checkout.html.erb @@ -0,0 +1,2 @@ +<%=render "order_form"%> +<%= link_to "Back to Cart", cart_path, class: "btn btn-outline-secondary sm-margin"%> \ No newline at end of file diff --git a/app/views/orders/confirmation.html.erb b/app/views/orders/confirmation.html.erb new file mode 100644 index 0000000000..d3d0f8152e --- /dev/null +++ b/app/views/orders/confirmation.html.erb @@ -0,0 +1,35 @@ +

Order Confirmation

+ + + + + + + + + + + + + + + + + + + <%@order.items.each_with_index do |item| %> + + + + + + + <%end%> + + + + <%# REMOVE h3, temp added to get a feel for style%> + + + +
Order ID: <%= @order.id %> Date & Time of order: <%= @order.created_at.strftime("%B %d, %T")%>Order Status: <%=@order.status %>
QuantityProductUnit PriceSubtotal
<%=item.quantity%><%=link_to item.product.name.upcase, product_path(item.product.id) %>$<%= "%.2f" % (item.product.price / 100.0)%>$<%= "%.2f" % item.subtotal %>

Total:

$<%= "%.2f" % @order.total%>

\ No newline at end of file diff --git a/app/views/orders/show.html.erb b/app/views/orders/show.html.erb new file mode 100644 index 0000000000..1212c6728d --- /dev/null +++ b/app/views/orders/show.html.erb @@ -0,0 +1,14 @@ + +
+

Order Summary

+

Order ID: <%=@order.id%>

+ +
+ diff --git a/app/views/orders/view_cart.html.erb b/app/views/orders/view_cart.html.erb new file mode 100644 index 0000000000..3b04a4c5ce --- /dev/null +++ b/app/views/orders/view_cart.html.erb @@ -0,0 +1,25 @@ +
+ <%if @order && @order.items.any? %> +

<%=@order.items.sum {|item| item.quantity}%> item<%="s" if @order.items.count != 1%> in cart

+
+ <%@order.items.each do |item| %> +
+
+

<%=item.product.name.upcase%>

+

quantity: <%=item.quantity%>

+

subtotal: $<%=item.subtotal%> +

<%=item.product.description%> + <% @product = item.product %> + <%= render partial: "/items/item_form", locals: {item: item, method_verb: :patch, action: "Update", url_path: item_path(item.id)} %> + <%= link_to "Remove item", item_path(item.id), method: :delete, class: "btn btn-outline-secondary sm-margin"%> +

+
+ <%end%> +
+ <%= link_to "Check Out", checkout_cart_path, class: "btn btn-outline-info btn-block"%> + <%=link_to "Keep shopping", products_path, class: "btn btn-secondary btn-block" %> + <%else%> +

0 items in cart

+

<%= link_to "Discover something intangible to fill it up", products_path%>

+ <%end%> +
\ No newline at end of file diff --git a/app/views/products/_form.html.erb b/app/views/products/_form.html.erb new file mode 100644 index 0000000000..896d4e946f --- /dev/null +++ b/app/views/products/_form.html.erb @@ -0,0 +1,39 @@ + +<%= form_with model: @product, class: form_class do |f| %> +
+
+ <%=f.label "Name"%> + <%=f.text_field :name, required: true, class: "form-control"%> +
+
+ <%=f.label "Price" %> + <%=f.number_field :price, :value => math, in: 1.0..1000.0, step: 0.01, required: true, class: "form-control" %> +
+
+
+
+ <%=f.label "Categories"%> + <%= collection_check_boxes(:product, :category_ids, Category.all, :id, :name)%> +
+
+
+
+ <%=f.label "Product description" %> + <%=f.text_field :description, required: true, class: "form-control"%> +
+
+ +
+
+ <%=f.label "Photo URL"%> + <%=f.text_field :photo_url, required: true, class: "form-control"%> +
+
+ <%=f.label "Stock"%> + <%=f.text_field :stock, required: true, class: "form-control"%> +
+
+
+ <%=f.submit action_name, class:"btn btn-info"%> +
+ <% end %> \ No newline at end of file diff --git a/app/views/products/edit.html.erb b/app/views/products/edit.html.erb new file mode 100644 index 0000000000..dad69e40b3 --- /dev/null +++ b/app/views/products/edit.html.erb @@ -0,0 +1,10 @@ +

Form for editing a product

+ +<%= render partial: "form", locals: { + + form_class: "edit-product", + intro_text: "Use this form to edit a product", + action_name: "Edit product", + math: @product.price / 100.0 +} %> + diff --git a/app/views/products/index.html.erb b/app/views/products/index.html.erb new file mode 100644 index 0000000000..0b567e3f7f --- /dev/null +++ b/app/views/products/index.html.erb @@ -0,0 +1,13 @@ +

Products

+
+ +<% Product.where(active: true).each do |product| %> + +
+ <%= link_to image_tag(product.photo_url), product_path(product.id) %> +

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

+

Price: $ <%= "%.2f" % (product.price / 100.0) %>

+
+<% end %> + +
\ No newline at end of file diff --git a/app/views/products/new.html.erb b/app/views/products/new.html.erb new file mode 100644 index 0000000000..a417e78cc1 --- /dev/null +++ b/app/views/products/new.html.erb @@ -0,0 +1,9 @@ +

New Product Form

+ + +<%= render partial: "form", locals: { + form_class: "new-product", + intro_text: "Use this form to create a new product", + action_name: "Create product", + math: nil +} %> diff --git a/app/views/products/show.html.erb b/app/views/products/show.html.erb new file mode 100644 index 0000000000..e3ef39f0a8 --- /dev/null +++ b/app/views/products/show.html.erb @@ -0,0 +1,58 @@ +
+

<%= @product.name.upcase %>

+ + <%= image_tag @product.photo_url, alt: "Image of the product", class: "detail" %> + +
+
+
Price: $ <%= "%.2f" % (@product.price / 100.0) %>
+ +
In-Stock: <%= @product.stock%>
+ + <%= render partial: "/items/item_form", locals: {item: Item.new, method_verb: :post, + action: "Add to Cart", url_path: product_items_path(@product.id)} %> + +
+
+
Categories
+ + <% if @product.categories.any? %> + + <% @product.categories.each do |category| %> + <%= link_to category.name, category_path(category.id) %> +
+ <% end %> + + <% else %> +

There are no categories assigned to this product

+ <% end %> +
+
+

<%= @product.description%>

+
+ +
+<%= link_to "Back to all products", products_path, class: "btn btn-outline-success" %> + +

+ Reviews +

+ +<% reviews = Review.where(product_id: @product.id)%> + <% if reviews.any? %> + <% reviews.each do |review| %> +

Rating: <%= review.rating%> +
+ Comment: <%= review.comment%>

+
+ <% end %> + <% else %> +

There are no reviews for this product

+ <% end %> +
+
+ <%= render partial: "/reviews/review_form"%> +
+ + + diff --git a/app/views/reviews/_review_form.html.erb b/app/views/reviews/_review_form.html.erb new file mode 100644 index 0000000000..79ece48736 --- /dev/null +++ b/app/views/reviews/_review_form.html.erb @@ -0,0 +1,14 @@ + +<%=form_with model: Review.new() , url: product_reviews_path(@product.id) do |f| %> +
+ <%= f.label :rating%> + <%= f.number_field :rating, required: true, in: 1..5, class: "form-control"%> +
+
+ <%= f.label :comment%> + <%=f.text_area :comment, class: "form-control"%> +
+
+ <%= f.submit class:"btn btn-sm btn-info" %> +
+<%end%> \ No newline at end of file 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..5badb2fde0 --- /dev/null +++ b/bin/rails @@ -0,0 +1,9 @@ +#!/usr/bin/env ruby +begin + load File.expand_path('../spring', __FILE__) +rescue LoadError => e + raise unless e.message.include?('spring') +end +APP_PATH = File.expand_path('../config/application', __dir__) +require_relative '../config/boot' +require 'rails/commands' diff --git a/bin/rake b/bin/rake new file mode 100755 index 0000000000..d87d5f5781 --- /dev/null +++ b/bin/rake @@ -0,0 +1,9 @@ +#!/usr/bin/env ruby +begin + load File.expand_path('../spring', __FILE__) +rescue LoadError => e + raise unless e.message.include?('spring') +end +require_relative '../config/boot' +require 'rake' +Rake.application.run diff --git a/bin/setup b/bin/setup new file mode 100755 index 0000000000..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..3e37da83c1 --- /dev/null +++ b/config/application.rb @@ -0,0 +1,27 @@ +require_relative 'boot' + +require 'rails/all' + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(*Rails.groups) + +module Betsy + class Application < Rails::Application + config.generators do |g| + # Force new test files to be generated in the minitest-spec style + g.test_framework :minitest, spec: true + # Always use .js files, never .coffee + g.javascript_engine :js + end + # Initialize configuration defaults for originally generated Rails version. + config.load_defaults 5.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. + + config.time_zone = "Pacific Time (US & Canada)" + 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..63c3c0b8a0 --- /dev/null +++ b/config/credentials.yml.enc @@ -0,0 +1 @@ +t/ENtHcwKeNjhJ4c1sF1wh0YxxTpaQbEsx7D2RbAhewDXRh+fFBUuagfgvVH3eQSsoPw+1LBfc+DdW/lv1D7Dx8DUdxQUGC0kaM2M6guq+3lJBq0egytiNngzdkkHkOsobamXWh3UmnxBGUhqKvT7PkNHxC2V8EI7t1QQgHYmRFgrDQULhsNKyKrEO6hSw+zHH8ZC/nShU0hCVGZGX2cWlxctjEBPKYaUpf0Loe8y06adxNa7XNta76LgrYktNeTnd0P8+hr0PJd9T8HuOybXbA9e42N6ePRR0Ufzq0s7JVR0bF3VKK+kHwqLv0Fi0YtlhCQyQWZ1/8NHhUGGlLFq0eKlXpQzP+I4l6gbfdx7Tbwoq5tcy1L5Nw8anXIITZ0pPnK1O7xRxcQ7Umi1REpRu9SRsnUDk5uDt2R--EbAv1Tcaewb/wts1--tDZAxADMR7aK+QVURKNRCQ== \ 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..ef8c60c371 --- /dev/null +++ b/config/initializers/omniauth.rb @@ -0,0 +1,4 @@ +# config/initializers/omniauth.rb +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..a98c109db2 --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,28 @@ +Rails.application.routes.draw do + get "/merchants/current", to: "merchants#current", as: "current_merchant" + get "homepages/index" + + resources :categories, only: [:new, :create, :index, :show] + resources :items, only: [:update, :destroy] + resources :merchants, only: [:index, :create, :show] + resources :orders, only: [:show, :update] + + get "/cart", to: "orders#view_cart", as: "cart" + get "/cart/checkout", to: "orders#checkout", as: "checkout_cart" + patch "/cart", to: "orders#purchase", as: "purchase_cart" + get "/cart/:id", to: "orders#confirmation", as: "order_confirmation" + + resources :products, except: [:destroy] do + resources :reviews, only: [:create] + resources :items, only: [:create, :update] + end + + root to: "homepages#index" + get "/auth/github", as: "github_login" + get "/auth/:provider/callback", to: "merchants#create", as: "auth_callback" + delete "/logout", to: "merchants#logout", as: "logout" + + patch "products/:id/toggle_inactive", to: "products#toggle_inactive", as: "toggle_inactive" + + # 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/categories_data.csv b/db/categories_data.csv new file mode 100644 index 0000000000..480f0b42ce --- /dev/null +++ b/db/categories_data.csv @@ -0,0 +1,6 @@ +name +Fun times +Rejunivation +Happiness +Magical +Imagination diff --git a/db/categories_generate_seeds.rb b/db/categories_generate_seeds.rb new file mode 100644 index 0000000000..41ad199949 --- /dev/null +++ b/db/categories_generate_seeds.rb @@ -0,0 +1,11 @@ +require "csv" + +categories = ["Fun times", "Rejunivation", "Happiness", "Magical", "Imagination"] + +CSV.open("db/categories_data.csv", "w", :write_headers => true, + :headers => ["name"]) do |csv| + 5.times do |i| + category = categories[i] + csv << [category] + end +end diff --git a/db/merchant_data.csv b/db/merchant_data.csv new file mode 100644 index 0000000000..2bd5a19afe --- /dev/null +++ b/db/merchant_data.csv @@ -0,0 +1,6 @@ +username,email,uid,provider +Farfetch'd,markus@upton.net,82039,github +Alakazam,barbera@bergnaum.info,90254,github +Caterpie,dominique@luettgenbruen.co,15965,github +Machop,mayeohara@skiles.info,81462,github +Nidorina,altha@grant.name,93644,github diff --git a/db/merchant_generate_seeds.rb b/db/merchant_generate_seeds.rb new file mode 100644 index 0000000000..3e610875d3 --- /dev/null +++ b/db/merchant_generate_seeds.rb @@ -0,0 +1,21 @@ +require "faker" +require "csv" + +# we already provide a filled out media_seeds.csv file, but feel free to +# run this script in order to replace it and generate a new one +# run using the command: +# $ ruby db/generate_seeds.rb +# if satisfied with this new media_seeds.csv file, recreate the db with: +# $ rails db:reset +# doesn't currently check for if titles are unique against each other + +CSV.open("merchant_data.csv", "w", :write_headers => true, + :headers => ["username", "email", "uid", "provider"]) do |csv| + 5.times do + username = Faker::Games::Pokemon.unique.name + email = Faker::Internet.unique.email + uid = Faker::Number.unique.number(5) + provider = "github" + csv << [username, email, uid, provider] + end +end diff --git a/db/migrate/20190501212559_create_products.rb b/db/migrate/20190501212559_create_products.rb new file mode 100644 index 0000000000..b0a8375270 --- /dev/null +++ b/db/migrate/20190501212559_create_products.rb @@ -0,0 +1,14 @@ +class CreateProducts < ActiveRecord::Migration[5.2] + def change + create_table :products do |t| + t.string :name + t.integer :price + t.string :description + t.string :photo_url + t.integer :stock + t.boolean :active, default: true + + t.timestamps + end + end +end diff --git a/db/migrate/20190501212702_create_reviews.rb b/db/migrate/20190501212702_create_reviews.rb new file mode 100644 index 0000000000..8248e3c6b3 --- /dev/null +++ b/db/migrate/20190501212702_create_reviews.rb @@ -0,0 +1,10 @@ +class CreateReviews < ActiveRecord::Migration[5.2] + def change + create_table :reviews do |t| + t.integer :rating + t.string :comment + + t.timestamps + end + end +end diff --git a/db/migrate/20190501212809_create_merchants.rb b/db/migrate/20190501212809_create_merchants.rb new file mode 100644 index 0000000000..7fb9e6f08e --- /dev/null +++ b/db/migrate/20190501212809_create_merchants.rb @@ -0,0 +1,12 @@ +class CreateMerchants < ActiveRecord::Migration[5.2] + def change + create_table :merchants do |t| + t.string :username + t.string :email + t.string :uid + t.string :provider + + t.timestamps + end + end +end diff --git a/db/migrate/20190501213002_create_orders.rb b/db/migrate/20190501213002_create_orders.rb new file mode 100644 index 0000000000..f97f32c72f --- /dev/null +++ b/db/migrate/20190501213002_create_orders.rb @@ -0,0 +1,14 @@ +class CreateOrders < ActiveRecord::Migration[5.2] + def change + create_table :orders do |t| + t.string :status + t.string :shopper_name + t.string :shopper_email + t.string :shopper_address + t.string :cc_four + t.string :cc_exp + + t.timestamps + end + end +end diff --git a/db/migrate/20190501213018_create_categories.rb b/db/migrate/20190501213018_create_categories.rb new file mode 100644 index 0000000000..6ccc3914a0 --- /dev/null +++ b/db/migrate/20190501213018_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/20190501213315_create_items.rb b/db/migrate/20190501213315_create_items.rb new file mode 100644 index 0000000000..1afce83ebd --- /dev/null +++ b/db/migrate/20190501213315_create_items.rb @@ -0,0 +1,9 @@ +class CreateItems < ActiveRecord::Migration[5.2] + def change + create_table :items do |t| + t.integer :quantity + + t.timestamps + end + end +end diff --git a/db/migrate/20190501215033_create_category_product_join_table.rb b/db/migrate/20190501215033_create_category_product_join_table.rb new file mode 100644 index 0000000000..83d3bce4d8 --- /dev/null +++ b/db/migrate/20190501215033_create_category_product_join_table.rb @@ -0,0 +1,8 @@ +class CreateCategoryProductJoinTable < ActiveRecord::Migration[5.2] + def change + create_table :categories_products do |t| + t.belongs_to :product, index: true + t.belongs_to :category, index: true + end + end +end diff --git a/db/migrate/20190501215635_add_product_id_to_review.rb b/db/migrate/20190501215635_add_product_id_to_review.rb new file mode 100644 index 0000000000..0cd70141a4 --- /dev/null +++ b/db/migrate/20190501215635_add_product_id_to_review.rb @@ -0,0 +1,5 @@ +class AddProductIdToReview < ActiveRecord::Migration[5.2] + def change + add_reference :reviews, :product, foreign_key: true + end +end diff --git a/db/migrate/20190501215709_add_merchant_id_to_product.rb b/db/migrate/20190501215709_add_merchant_id_to_product.rb new file mode 100644 index 0000000000..77257b8727 --- /dev/null +++ b/db/migrate/20190501215709_add_merchant_id_to_product.rb @@ -0,0 +1,5 @@ +class AddMerchantIdToProduct < ActiveRecord::Migration[5.2] + def change + add_reference :products, :merchant, foreign_key: true + end +end diff --git a/db/migrate/20190501215729_add_product_id_and_order_id_to_item.rb b/db/migrate/20190501215729_add_product_id_and_order_id_to_item.rb new file mode 100644 index 0000000000..b50f9836d3 --- /dev/null +++ b/db/migrate/20190501215729_add_product_id_and_order_id_to_item.rb @@ -0,0 +1,6 @@ +class AddProductIdAndOrderIdToItem < ActiveRecord::Migration[5.2] + def change + add_reference :items, :product, foreign_key: true + add_reference :items, :order, foreign_key: true + end +end diff --git a/db/migrate/20190505031625_remove_status_from_orders.rb b/db/migrate/20190505031625_remove_status_from_orders.rb new file mode 100644 index 0000000000..f855464443 --- /dev/null +++ b/db/migrate/20190505031625_remove_status_from_orders.rb @@ -0,0 +1,5 @@ +class RemoveStatusFromOrders < ActiveRecord::Migration[5.2] + def change + remove_column :orders, :status, :string + end +end diff --git a/db/migrate/20190505031640_add_status_to_orders.rb b/db/migrate/20190505031640_add_status_to_orders.rb new file mode 100644 index 0000000000..e0eaac3cfd --- /dev/null +++ b/db/migrate/20190505031640_add_status_to_orders.rb @@ -0,0 +1,5 @@ +class AddStatusToOrders < ActiveRecord::Migration[5.2] + def change + add_column :orders, :status, :string, default: "pending" + end +end diff --git a/db/products_data.csv b/db/products_data.csv new file mode 100644 index 0000000000..0015a49609 --- /dev/null +++ b/db/products_data.csv @@ -0,0 +1,22 @@ +name,price,stock,description,photo_url +High Fives,3990,3,Best highfives! When your down highfives will make you touch sky! ,https://i.ibb.co/jD3rcp0/camylla-battani-784361-unsplash.jpg +Cup of Sunshine,4277,6,Brighest sunshine overflowing the biggest cup.,https://i.ibb.co/16kjtyH/mi-pham-223464-unsplash.jpg +Bucket of Sunshine,2089,3,When the biggest cup is not enough grap a bucket of our brightest sunshine!,https://i.ibb.co/SBcwL51/hemerson-coelho-1364589-unsplash.jpg +Sleep Revival,4799,5,Kids? Work? Sleep revival is the cure all. Sleep 10 hrs in just 30 mins!,https://i.ibb.co/qsyp1TL/isaac-davis-598918-unsplash.jpg +Relaxation,3361,8,Relaxation not constipation. Calm those nerves and treat your self well.,https://i.ibb.co/ZS6t12d/allef-vinicius-111554-unsplash.jpg +Energy,1131,6,What!!!!! Lets GO! Engergy yo! On demand or in the sand.,https://i.ibb.co/qW7q6PW/maria-fernanda-gonzalez-461521-unsplash.jpg +A Good Hair Day,637,7,For that special occasion when you really do and need great hair.,https://i.ibb.co/ZhfmGX8/bruno-nascimento-149663-unsplash.jpg +Pause Time,1758,4,Stop time at the drop of a dime. This product is a gold mine,https://i.ibb.co/RzHDBvx/ahmadreza-sajadi-10140-unsplash.jpg +Freshly Showered,4963,8,"Projects are hard, make your life easier by feeling freshly showered.",https://i.ibb.co/0D9th8Z/michael-podger-252489-unsplash.jpg +Teleport Into Bed,3216,6,Open incase of emergency.,https://i.ibb.co/nk5FkwV/diego-ph-493991-unsplash.jpg +Ada Starter Kit,2397,5,"The ADA starter kit features the things that hardworking Adies need, relaxation, reflection, and a calm mind.",https://i.ibb.co/ssgbQqH/ishan-seefromthesky-277746-unsplash.jpg +Ada Best Seller Pack,4517,8,Take some time to yourself. Work hard and play hard.,https://i.ibb.co/XCc7kxc/recal-media-83365-unsplash.jpg +Cup of Rainbows,4282,4,Find something magical in your day and in yourself.,https://i.ibb.co/LprTnc8/alistair-macrobert-633034-unsplash.jpg +Puppy play Pack,2803,8,Unwind with Puppy Time. You'll thank us later.,https://i.ibb.co/M1xGDJ0/bharathi-kannan-1260953-unsplash.jpg +kitten cuddles,4928,7,Stop Kitten Yourself! Take some time to unwind and cuddle.,https://i.ibb.co/GdT7fWn/kote-puerto-771605-unsplash.jpg +Giggle Party,3250,3,Sometimes unwinding just means giggling...HARD.,https://i.ibb.co/pP0xbMM/dan-cook-508166-unsplash.jpg +Bestfriend Time,2332,11,Self care is important so take some time to catch up!,https://i.ibb.co/Jd4nkWQ/ian-schneider-64307-unsplash.jpg +Juicy Secret,1170,5,The Juciest Secret to put some pep in your step.,https://i.ibb.co/PN6tfQY/marcela-rogante-432403-unsplash.jpg +Inside Joke Moment,4141,4,Take a pause and lighten the mood with an inside joke moment,https://i.ibb.co/7WNs7Y3/carlos-pimentel-1206339-unsplash.jpg +Bondfire Time,2976,4,Sometimes all you need is a BONDfire to reconnect with friends.,https://i.ibb.co/JR78YWq/mike-erskine-144525-unsplash.jpg +"Hugs from Elle's Mom",1253, 752,"With over five decades of experience, Elle's Mom is an expert in filling her hugs with love.",https://lwm-a2.azureedge.net/uploads/2018/08/Jen-HatmakerFeat1.jpg \ No newline at end of file diff --git a/db/products_generate_seeds.rb b/db/products_generate_seeds.rb new file mode 100644 index 0000000000..8b020ef49a --- /dev/null +++ b/db/products_generate_seeds.rb @@ -0,0 +1,19 @@ +require "faker" +require "csv" + +CSV.open("products_data.csv", "w", :write_headers => true, + :headers => ["name", "price", "description", "photo_url", "stock"]) do |csv| + intangibles = [ + "high fives", "cup of sunshine", "bucket of sunshine", "sleep revival", "relaxation", "energy", "a good hair day", "pause time", "freshly showered", "teleport into bed", "ada starter kit", "ada best seller pack", "cup of rainbows", "puppy play pack", "kitten cuddles", "giggle party", "bestfriend time", "juicy secret", "inside joke moment", "bondfire time" + ] + 20.times do |i| + + name = intangibles[i] + price = rand(500..5000) + description = Faker::Movies::HitchhikersGuideToTheGalaxy.quote + photo_url = "https://placekitten.com/g/200/300" + stock = rand(1..15) + + csv << [name, price, description, photo_url, stock] + end +end diff --git a/db/reviews_data.csv b/db/reviews_data.csv new file mode 100644 index 0000000000..0340bfffb6 --- /dev/null +++ b/db/reviews_data.csv @@ -0,0 +1,41 @@ +rating,comment +3,design does not hold up on rugged use and did not reduce fatique. +5,"brightened my day. It was also promises to make shooting daytime and nighttime action simpler and professional-looking, providing the budding photographer with new and enticing reasons to hone their skills." +3,design does not hold up on rugged use and did not reduce fatique. +4,"brightened my day. It was also promises to make shooting daytime and nighttime action simpler and professional-looking, providing the budding photographer with new and enticing reasons to hone their skills." +1,design does not hold up on rugged use and did not reduce fatique. +4,"brightened my day. It was also promises to make shooting daytime and nighttime action simpler and professional-looking, providing the budding photographer with new and enticing reasons to hone their skills." +4,"brightened my day. It was also promises to make shooting daytime and nighttime action simpler and professional-looking, providing the budding photographer with new and enticing reasons to hone their skills." +5,"brightened my day. It was also promises to make shooting daytime and nighttime action simpler and professional-looking, providing the budding photographer with new and enticing reasons to hone their skills." +1,design does not hold up on rugged use and did not reduce fatique. +2,design does not hold up on rugged use and did not reduce fatique. +5,"brightened my day. It was also promises to make shooting daytime and nighttime action simpler and professional-looking, providing the budding photographer with new and enticing reasons to hone their skills." +4,"brightened my day. It was also promises to make shooting daytime and nighttime action simpler and professional-looking, providing the budding photographer with new and enticing reasons to hone their skills." +3,design does not hold up on rugged use and did not reduce fatique. +5,"brightened my day. It was also promises to make shooting daytime and nighttime action simpler and professional-looking, providing the budding photographer with new and enticing reasons to hone their skills." +2,design does not hold up on rugged use and did not reduce fatique. +5,"brightened my day. It was also promises to make shooting daytime and nighttime action simpler and professional-looking, providing the budding photographer with new and enticing reasons to hone their skills." +1,design does not hold up on rugged use and did not reduce fatique. +2,design does not hold up on rugged use and did not reduce fatique. +5,"brightened my day. It was also promises to make shooting daytime and nighttime action simpler and professional-looking, providing the budding photographer with new and enticing reasons to hone their skills." +5,"brightened my day. It was also promises to make shooting daytime and nighttime action simpler and professional-looking, providing the budding photographer with new and enticing reasons to hone their skills." +3,design does not hold up on rugged use and did not reduce fatique. +1,design does not hold up on rugged use and did not reduce fatique. +1,design does not hold up on rugged use and did not reduce fatique. +3,design does not hold up on rugged use and did not reduce fatique. +3,design does not hold up on rugged use and did not reduce fatique. +5,"brightened my day. It was also promises to make shooting daytime and nighttime action simpler and professional-looking, providing the budding photographer with new and enticing reasons to hone their skills." +2,design does not hold up on rugged use and did not reduce fatique. +2,design does not hold up on rugged use and did not reduce fatique. +3,design does not hold up on rugged use and did not reduce fatique. +3,design does not hold up on rugged use and did not reduce fatique. +4,"brightened my day. It was also promises to make shooting daytime and nighttime action simpler and professional-looking, providing the budding photographer with new and enticing reasons to hone their skills." +5,"brightened my day. It was also promises to make shooting daytime and nighttime action simpler and professional-looking, providing the budding photographer with new and enticing reasons to hone their skills." +1,design does not hold up on rugged use and did not reduce fatique. +2,design does not hold up on rugged use and did not reduce fatique. +3,design does not hold up on rugged use and did not reduce fatique. +5,"brightened my day. It was also promises to make shooting daytime and nighttime action simpler and professional-looking, providing the budding photographer with new and enticing reasons to hone their skills." +1,design does not hold up on rugged use and did not reduce fatique. +1,design does not hold up on rugged use and did not reduce fatique. +5,"brightened my day. It was also promises to make shooting daytime and nighttime action simpler and professional-looking, providing the budding photographer with new and enticing reasons to hone their skills." +1,design does not hold up on rugged use and did not reduce fatique. diff --git a/db/reviews_generate_seeds.rb b/db/reviews_generate_seeds.rb new file mode 100644 index 0000000000..06eefb9141 --- /dev/null +++ b/db/reviews_generate_seeds.rb @@ -0,0 +1,15 @@ +require "faker" +require "csv" + +CSV.open("db/reviews_data.csv", "w", :write_headers => true, + :headers => ["rating", "comment"]) do |csv| + 40.times do |i| + rating = rand(1..5) + if rating > 3 + comment = " brightened my day. It was also promises to make shooting daytime and nighttime action simpler and professional-looking, providing the budding photographer with new and enticing reasons to hone their skills." + else + comment = " design does not hold up on rugged use and did not reduce fatique." + end + csv << [rating, comment] + end +end diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 0000000000..b57efe7098 --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,87 @@ +# 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: 2019_05_05_031640) 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.index ["category_id"], name: "index_categories_products_on_category_id" + t.index ["product_id"], name: "index_categories_products_on_product_id" + end + + create_table "items", force: :cascade do |t| + t.integer "quantity" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.bigint "product_id" + t.bigint "order_id" + t.index ["order_id"], name: "index_items_on_order_id" + t.index ["product_id"], name: "index_items_on_product_id" + end + + create_table "merchants", force: :cascade do |t| + t.string "username" + t.string "email" + t.string "uid" + t.string "provider" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "orders", force: :cascade do |t| + t.string "shopper_name" + t.string "shopper_email" + t.string "shopper_address" + t.string "cc_four" + t.string "cc_exp" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "status", default: "pending" + end + + create_table "products", force: :cascade do |t| + t.string "name" + t.integer "price" + t.string "description" + t.string "photo_url" + t.integer "stock" + t.boolean "active", default: true + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.bigint "merchant_id" + t.index ["merchant_id"], name: "index_products_on_merchant_id" + end + + create_table "reviews", force: :cascade do |t| + t.integer "rating" + t.string "comment" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.bigint "product_id" + t.index ["product_id"], name: "index_reviews_on_product_id" + end + + add_foreign_key "items", "orders" + add_foreign_key "items", "products" + add_foreign_key "products", "merchants" + add_foreign_key "reviews", "products" +end diff --git a/db/seeds.rb b/db/seeds.rb new file mode 100644 index 0000000000..d72615d044 --- /dev/null +++ b/db/seeds.rb @@ -0,0 +1,48 @@ +# 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" +failed_to_save = [] +CSV.open("db/merchant_data.csv", headers: true).each_with_index do |line, i| + merchant = Merchant.new( + username: line["username"], + email: line["email"], + uid: line["uid"], + provider: line["provider"], + ) + merchant.save ? (puts "#{i + 1} merchant ") : failed_to_save << merchant +end + +CSV.open("db/products_data.csv", headers: true).each_with_index do |line, i| + merchant = Merchant.find(rand(1..Merchant.count)) + product = merchant.products.create(name: line["name"].upcase, + price: line["price"], + description: line["description"], + photo_url: line["photo_url"], + stock: line["stock"]) + product.valid? ? (puts "#{i + 1} product") : failed_to_save << product +end + +CSV.open("db/reviews_data.csv", headers: true).each_with_index do |line, i| + product = Product.find(rand(1..Product.count)) + review = Review.new(rating: line["rating"], + comment: product.name + " " + line["comment"]) + product.reviews << review + review.valid? ? (puts "#{i + 1} review") : failed_to_save << review +end + +i = 0 +CSV.open("db/categories_data.csv", headers: true).each do |line| + category = Category.new(name: line["name"]) + 10.times do |j| + product = Product.find(rand(1..Product.count)) + product.categories << category unless product.categories.include?(category) + category.valid? ? (puts "#{i += 1} category-product relationships") : failed_to_save << review + end +end +p failed_to_save +puts "#{failed_to_save.count} failed to save" 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/review_data.csv b/review_data.csv new file mode 100644 index 0000000000..9d170832fc --- /dev/null +++ b/review_data.csv @@ -0,0 +1,41 @@ +review,rating +2,Freshly showered design does not hold up on rugged use and did not reduce fatique. +3,Giggle party design does not hold up on rugged use and did not reduce fatique. +4,"Juicy secret brightened my day. It was also promises to make shooting daytime and nighttime action simpler and professional-looking, providing the budding photographer with new and enticing reasons to hone their skills." +4,"Bondfire time brightened my day. It was also promises to make shooting daytime and nighttime action simpler and professional-looking, providing the budding photographer with new and enticing reasons to hone their skills." +4,"Ada starter kit brightened my day. It was also promises to make shooting daytime and nighttime action simpler and professional-looking, providing the budding photographer with new and enticing reasons to hone their skills." +1,A good hair day design does not hold up on rugged use and did not reduce fatique. +3,Puppy play pack design does not hold up on rugged use and did not reduce fatique. +5,"Bestfriend time brightened my day. It was also promises to make shooting daytime and nighttime action simpler and professional-looking, providing the budding photographer with new and enticing reasons to hone their skills." +5,"Inside joke moment brightened my day. It was also promises to make shooting daytime and nighttime action simpler and professional-looking, providing the budding photographer with new and enticing reasons to hone their skills." +1,Cup of sunshine design does not hold up on rugged use and did not reduce fatique. +5,"Pause time brightened my day. It was also promises to make shooting daytime and nighttime action simpler and professional-looking, providing the budding photographer with new and enticing reasons to hone their skills." +2,A good hair day design does not hold up on rugged use and did not reduce fatique. +3,Inside joke moment design does not hold up on rugged use and did not reduce fatique. +2,Juicy secret design does not hold up on rugged use and did not reduce fatique. +2,Ada starter kit design does not hold up on rugged use and did not reduce fatique. +3,A good hair day design does not hold up on rugged use and did not reduce fatique. +3,Kitten cuddles design does not hold up on rugged use and did not reduce fatique. +4,"Ada starter kit brightened my day. It was also promises to make shooting daytime and nighttime action simpler and professional-looking, providing the budding photographer with new and enticing reasons to hone their skills." +5,"Cup of rainbows brightened my day. It was also promises to make shooting daytime and nighttime action simpler and professional-looking, providing the budding photographer with new and enticing reasons to hone their skills." +1,Relaxation design does not hold up on rugged use and did not reduce fatique. +2,Giggle party design does not hold up on rugged use and did not reduce fatique. +4,"Bucket of sunshine brightened my day. It was also promises to make shooting daytime and nighttime action simpler and professional-looking, providing the budding photographer with new and enticing reasons to hone their skills." +5,"Ada starter kit brightened my day. It was also promises to make shooting daytime and nighttime action simpler and professional-looking, providing the budding photographer with new and enticing reasons to hone their skills." +3,Freshly showered design does not hold up on rugged use and did not reduce fatique. +4,"Freshly showered brightened my day. It was also promises to make shooting daytime and nighttime action simpler and professional-looking, providing the budding photographer with new and enticing reasons to hone their skills." +2,Cup of rainbows design does not hold up on rugged use and did not reduce fatique. +4,"Bestfriend time brightened my day. It was also promises to make shooting daytime and nighttime action simpler and professional-looking, providing the budding photographer with new and enticing reasons to hone their skills." +5,"Relaxation brightened my day. It was also promises to make shooting daytime and nighttime action simpler and professional-looking, providing the budding photographer with new and enticing reasons to hone their skills." +1,Ada best seller pack design does not hold up on rugged use and did not reduce fatique. +3,Ada best seller pack design does not hold up on rugged use and did not reduce fatique. +1,Puppy play pack design does not hold up on rugged use and did not reduce fatique. +2,Giggle party design does not hold up on rugged use and did not reduce fatique. +2,Teleport into bed design does not hold up on rugged use and did not reduce fatique. +1,Cup of sunshine design does not hold up on rugged use and did not reduce fatique. +5,"Bondfire time brightened my day. It was also promises to make shooting daytime and nighttime action simpler and professional-looking, providing the budding photographer with new and enticing reasons to hone their skills." +2,Ada starter kit design does not hold up on rugged use and did not reduce fatique. +2,Juicy secret design does not hold up on rugged use and did not reduce fatique. +4,"Bestfriend time brightened my day. It was also promises to make shooting daytime and nighttime action simpler and professional-looking, providing the budding photographer with new and enticing reasons to hone their skills." +5,"Giggle party brightened my day. It was also promises to make shooting daytime and nighttime action simpler and professional-looking, providing the budding photographer with new and enticing reasons to hone their skills." +1,Cup of rainbows design does not hold up on rugged use and did not reduce fatique. diff --git a/storage/.keep b/storage/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/temp.rb b/temp.rb new file mode 100644 index 0000000000..62d02d5528 --- /dev/null +++ b/temp.rb @@ -0,0 +1 @@ +{ "paid" => { "615724322" => [Item.find(477087474)] }, "complete" => { "1035625630" => [Item.find(845260767), Item.find(728299111)] } } 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..8c0cae5de4 --- /dev/null +++ b/test/controllers/categories_controller_test.rb @@ -0,0 +1,99 @@ +require "test_helper" + +describe CategoriesController do + describe "index" do + it "should get index" do + # Act + get categories_path + + # Assert + must_respond_with :success + end + end + + describe "show" do + it "can get a valid category" do + + # Act + get category_path(categories(:category_1).id) + + # Assert + must_respond_with :success + end + + it "will redirect and give a flash notice for an invalid category" do + + # Act + get category_path(-1) + + # Assert + must_respond_with :redirect + expect(flash[:error]).must_equal "Unknown category" + end + end + + describe "new" do + describe "logged in merchant" do + it "succeeds" do + perform_login + get new_category_path + + must_respond_with :success + end + end + + describe "logged out" do + it "redirects and give flash notice" do + get new_category_path + + must_respond_with :redirect + must_redirect_to root_path + expect(flash[:error]).must_equal "You must be logged in to add a new category" + end + end + end + + describe "create" do + describe "logged in merchant" do + it "creates a category with valid data" do + perform_login + new_category = {category: {name: "Magic feeling"}} + expect { + post categories_path, params: new_category + }.must_change "Category.count", 1 + # Elle's note: + # For syntax helping to understand strings vs symbols as keys + # expect({ post categories_path, params: new_category }).must_change("Category.count", 1) + + expect(flash[:success]).must_equal "Category added successfully" + must_respond_with :redirect + must_redirect_to categories_path + end + + it "renders bad_request and does not update the DB for bogus data" do + perform_login + bad_category_name = {category: {name: nil}} + + expect { + post categories_path, params: bad_category_name + }.wont_change "Category.count" + + must_respond_with :bad_request + expect(flash[:error]).must_include "Could not add new category" + expect(flash[:name]).must_include "can't be blank" + end + end + + describe "logged out" do + it "cannot create a category with valid data" do + new_category = {category: {name: "Magic feeling"}} + expect { + post categories_path, params: new_category + }.wont_change "Category.count" + + must_respond_with :bad_request + expect(flash[:error]).must_equal "You must be logged in to create a new category" + end + end + end +end diff --git a/test/controllers/homepages_controller_test.rb b/test/controllers/homepages_controller_test.rb new file mode 100644 index 0000000000..7395af3dec --- /dev/null +++ b/test/controllers/homepages_controller_test.rb @@ -0,0 +1,9 @@ +require "test_helper" + +describe HomepagesController do + it "should get index" do + get homepages_index_url + value(response).must_be :success? + end + +end diff --git a/test/controllers/items_controller_test.rb b/test/controllers/items_controller_test.rb new file mode 100644 index 0000000000..809dc786c0 --- /dev/null +++ b/test/controllers/items_controller_test.rb @@ -0,0 +1,160 @@ +require "test_helper" + +describe ItemsController do + describe "create" do + let(:product) { products(:product_1) } + let(:item_params) { {item: {quantity: 2}} } + it "will create a new item" do + expect { + post product_items_path(product.id), params: item_params + }.must_change "Item.count", 1 + + expect(flash[:success]).must_equal "#{product.name} (total quantity: #{item_params[:item][:quantity]}) Successfully Added To Cart" + + must_respond_with :redirect + must_redirect_to product_path(product.id) + end + + it "will create a new order if no order in session" do + expect { + post product_items_path(product.id), params: item_params + }.must_change "Order.count", 1 + + expect(flash[:success]).must_equal "#{product.name} (total quantity: #{item_params[:item][:quantity]}) Successfully Added To Cart" + + must_respond_with :redirect + must_redirect_to product_path(product.id) + end + + it "will use current order if cart_id in session " do + expect { + post product_items_path(products(:product_2)), params: item_params + }.must_change "Order.count", 1 + + expect { + post product_items_path(product.id), params: item_params + }.must_change "Order.count", 0 + + expect(flash[:success]).must_equal "#{product.name} (total quantity: #{item_params[:item][:quantity]}) Successfully Added To Cart" + + must_respond_with :redirect + must_redirect_to product_path(product.id) + end + + it "will not create a new item if product is not valid" do + product_id = -1 + expect { + post product_items_path(product_id), params: item_params + }.must_change "Item.count", 0 + + expect(flash[:error]).must_equal "Could Not Add To Cart: product not available" + + must_respond_with :redirect + must_redirect_to products_path + end + + it "will not create an item for with requested quantitiy greater than the products stock" do + item_params[:item][:quantity] = product.stock + 1 + expect { + post product_items_path(product.id), params: item_params + }.must_change "Item.count", 0 + + expect(flash[:error]).must_equal "Could Not Add To Cart: quantity selected is more than available stock" + + must_respond_with :bad_request + end + + it "will find an item if already created and increase its quantity rather than make new" do + expect { + post product_items_path(product.id), params: item_params + }.must_change "Item.count", 1 + + item = Item.find_by(product_id: product.id, order_id: session[:cart_id]) + expect(item.quantity).must_equal item_params[:item][:quantity] + + expect { + post product_items_path(product.id), params: item_params + }.must_change "Item.count", 0 + + item = Item.find_by(product_id: product.id, order_id: session[:cart_id]) + expect(item.quantity).must_equal item_params[:item][:quantity] * 2 + end + + it "will not create an item with in invalid quantity" do + item_params[:item][:quantity] = nil + expect { + post product_items_path(product.id), params: item_params + }.must_change "Item.count", 0 + + expect(flash[:error]).must_equal "Could Not Add To Cart" + + must_respond_with :bad_request + end + end + + describe "update" do + let(:item) { items(:item_1) } + let(:item_params) { {item: {quantity: 2}} } + it "will update a valid item if product has enough stock" do + expect { + patch item_path(item.id), params: item_params + }.wont_change "Item.count" + must_respond_with :redirect + must_redirect_to cart_path + expect(flash[:success]).must_equal "#{item.product.name} successfully updated" + end + + it "will not update a product if update of quantity exceeds product stock" do + products(:product_1).update(stock: 1) + + expect { + patch item_path(item.id), params: item_params + }.wont_change "Item.count" + must_respond_with :redirect + must_redirect_to cart_path + expect(flash[:error]).must_equal "Unable to update item" + end + + it "will show flash message and redirect updated item is invalid" do + item_params[:item][:quantity] = -1 + expect { + patch item_path(item.id), params: item_params + }.wont_change "Item.count" + must_respond_with :redirect + must_redirect_to cart_path + expect(flash[:error]).must_equal "Unable to update item" + end + + it "will show flash message and redirect if item is not found " do + invalid_id = -1 + expect { + patch item_path(invalid_id), params: item_params + }.wont_change "Item.count" + must_respond_with :redirect + must_redirect_to cart_path + expect(flash[:error]).must_equal "Unable to update item" + end + end + + describe "destroy" do + let(:item) { items(:item_1) } + it "will destroy a valid item" do + expect { + delete item_path(item.id) + }.must_change "Item.count", -1 + + must_respond_with :redirect + must_redirect_to cart_path + end + + it "will not destroy an item given invalid id" do + invalid_id = -1 + expect { + delete item_path(invalid_id) + }.must_change "Item.count", 0 + + must_respond_with :redirect + must_redirect_to cart_path + end + end +end diff --git a/test/controllers/merchants_controller_test.rb b/test/controllers/merchants_controller_test.rb new file mode 100644 index 0000000000..d1c2aeedd6 --- /dev/null +++ b/test/controllers/merchants_controller_test.rb @@ -0,0 +1,134 @@ +require "test_helper" +require "pry" + +describe MerchantsController do + describe "index" do + it "should get index" do + # Act + get merchants_path + + # Assert + must_respond_with :success + end + end + + describe "show" do + it "can get a valid merchant" do + + # Act + get merchant_path(merchants(:merchant_1).id) + + # Assert + must_respond_with :success + end + + it "will redirect and give a flash notice for an invalid merchant" do + + # Act + get merchant_path(-1) + + # Assert + must_respond_with :redirect + expect(flash[:error]).must_equal "Unknown merchant" + end + end + + describe "login" do + it "log in an existing merchant" do + start_count = Merchant.count + merchant = merchants(:merchant_1) + # binding.pry + perform_login(merchant) + session[:merchant_id].must_equal merchant.id + + # Should *not* have created a new user + Merchant.count.must_equal start_count + end + + it "create a new merchant if the user has not previously been saved in our database" do + # Arrange + start_count = Merchant.count + + # Act + # new_merch = Merchant.create(new_merchant_params) + new_merch = Merchant.new(provider: "github", uid: "443356", username: "ellesmom", email: "jackie@who.net") + + OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new(mock_auth_hash(new_merch)) + get auth_callback_path(:github) + + # Assert + Merchant.count.must_equal start_count + 1 + must_respond_with :redirect + + expect(flash[:success]).must_equal "Logged in as new merchant #{new_merch.username}" + + must_redirect_to root_path + end + + it "give an error if a new merchant fails to save after validation" do + start_count = Merchant.count + + new_merch = Merchant.new(provider: "github", uid: "", username: "ellesmom", email: "jackie@who.net") + + OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new(mock_auth_hash(new_merch)) + get auth_callback_path(:github) + + # Assert + Merchant.count.must_equal start_count + + expect(flash[:error]).must_equal "Could not create new merchant account: {:uid=>[\"can't be blank\"]}" + + must_respond_with :redirect + must_redirect_to root_path + end + end + + describe "logout" do + it "successfully log out a logged-in merchant" do + # Arrange + start_count = Merchant.count + merchant = merchants(:merchant_1) + # binding.pry + perform_login + + # Act + delete logout_path + + # Assert + # check flash message + expect(flash[:success]).must_equal "Successfully logged out." + # check session id gets set to nil + session[:merchant_id].must_be_nil + # check redirect to root_path + must_respond_with :redirect + + must_redirect_to root_path + # Should *not* have created or deleted a user + Merchant.count.must_equal start_count + end + end + describe "current" do + describe "logged in merchant" do + it "should get showpage" do + # Act + get current_merchant_path + + # Assert + must_respond_with :found + end + end + + describe "not logged in" do + it "will redirect and give a flash notice when not logged in" do + + # Act + get current_merchant_path + + # Assert + must_respond_with :redirect + must_redirect_to root_path + expect(flash[:error]).must_equal "You must be logged to view this page" + end + 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..01fe211566 --- /dev/null +++ b/test/controllers/orders_controller_test.rb @@ -0,0 +1,343 @@ +require "test_helper" + +describe OrdersController do + # removed index path and controller rather than using it just to redirect + # describe "index" do + # it "will redirect all calls to home page" do + # get orders_path + # must_respond_with :redirect + # must_redirect_to root_path + # end + # end + describe "show" do + let(:order) { orders(:order_1) } + describe "not logged in" do + it "will redirct to root path with flash message" do + get order_path(order.id) + + expect(flash[:error]).must_equal "You must be logged to view this page" + + must_respond_with :redirect + must_redirect_to root_path + end + end + + describe "as a logged in merchant" do + before do + perform_login(merchants(:merchant_2)) + end + it "will show order page if merchant has a product in the order" do + get order_path(order.id) + + must_respond_with :success + end + + it "will redirect to merchant dashboard w/flashif no products for order belong to mechant" do + perform_login + get order_path(order.id) + + expect(flash[:error]).must_equal "Can not view order page. No items sold by merchant." + must_respond_with :redirect + must_redirect_to current_merchant_path + end + + it "if given invalid id and user is logged in redrect to dashboard w/ flash" do + order_id = -1 + get order_path(-1) + + expect(flash[:error]).must_equal "Can not view order page. No items sold by merchant." + must_respond_with :redirect + must_redirect_to current_merchant_path + end + end + end + + describe "update" do + let(:order) { orders(:order_2) } + let(:order_params) { { order: { status: "complete" } } } + describe "not logged in user" do + it "will redirect home with flash and NOT update order" do + expect(order.status).must_equal "paid" + + expect { + patch order_path(order.id), params: order_params + }.wont_change "Order.count" + + order.reload + + expect(order.status).must_equal "paid" + expect(flash[:error]).must_equal "Can not update order" + + must_respond_with :redirect + must_redirect_to root_path + end + end + + describe "as a loggedd in merchant" do + before do + perform_login(merchants(:merchant_2)) + end + it "will update order if merchant has items on order and order is valid" do + expect(order.status).must_equal "paid" + expect { + patch order_path(order.id), params: order_params + }.wont_change "Order.count" + + order.reload + + expect(flash[:success]).must_equal "Order updated" + expect(order.status).must_equal "complete" + must_respond_with :redirect + must_redirect_to current_merchant_path + end + + it "will not update if merchant does not have items on order" do + perform_login + expect(order.status).must_equal "paid" + + expect { + patch order_path(order.id), params: order_params + }.wont_change "Order.count" + + order.reload + + expect(order.status).must_equal "paid" + expect(flash[:error]).must_equal "Can not update order" + must_respond_with :redirect + must_redirect_to root_path + end + + it "will redriect home with flash if order is not valid" do + invalid_id = -1 + expect { + patch order_path(-1), params: order_params + }.wont_change "Order.count" + expect(flash[:error]).must_equal "Can not update order" + must_respond_with :redirect + must_redirect_to root_path + end + end + end + describe "view_cart" do + it "will respond with success if no cart is stored in session" do + get cart_path + must_respond_with :success + expect(session[:cart_id]).must_be_nil + end + + let(:product) { products(:product_1) } + let(:item_params) { { item: { quantity: 2 } } } + it "will respond with success if cart stored in session" do + post product_items_path(product.id), params: item_params + get cart_path + must_respond_with :success + expect(session[:cart_id]).wont_be_nil + end + end + + describe "confirmation" do + let(:product) { products(:product_1) } + let(:product2) { products(:product_2) } + let(:item_params) { { item: { quantity: 2 } } } + let(:cart_params) { + { order: { shopper_name: "susie", + shopper_email: "dalmation@susy.org", + shopper_address: "44550 mailing address", + cc_all: "234443434", + cc_exp: "03022020" } } + } + before do + post product_items_path(product.id), params: item_params + post product_items_path(product2.id), params: item_params + @order = Order.find(session[:cart_id]) + end + describe "following succesful purchase of cart" do + before do + patch purchase_cart_path, params: cart_params + end + it "will respond with success" do + get order_confirmation_path(@order.id) + must_respond_with :success + end + + it "will reset confirmation in session to nil" do + expect(session[:confirmation]).must_equal @order.id + get order_confirmation_path(@order.id) + expect(session[:confirmation]).must_be_nil + end + end + + describe "not following purchase of cart" do + it "will redirect to root and not make changes to session[:confirmation]" do + expect(session[:confirmation]).must_be_nil + + get order_confirmation_path(@order.id) + + expect(session[:confirmation]).must_be_nil + expect(flash[:error]).must_equal "Checkout cart to view confirmation page" + + must_respond_with :redirect + must_redirect_to root_path + end + end + end + + describe "checkout" do + let(:product) { products(:product_1) } + let(:item_params) { { item: { quantity: 2 } } } + it "will respond with success if cart stored in session" do + post product_items_path(product.id), params: item_params + get checkout_cart_path + must_respond_with :success + expect(session[:cart_id]).wont_be_nil + end + + it "will redirect if no valid cart in session" do + get checkout_cart_path + must_respond_with :redirect + must_redirect_to cart_path + expect(flash[:error]).must_equal "Unable to checkout cart" + expect(session[:cart_id]).must_be_nil + end + end + + describe "purchase" do + let(:product) { products(:product_1) } + let(:product2) { products(:product_2) } + let(:item_params) { { item: { quantity: 2 } } } + let(:cart_params) { + { order: { shopper_name: "susie", + shopper_email: "dalmation@susy.org", + shopper_address: "44550 mailing address", + cc_all: "234443434", + cc_exp: "03022020" } } + } + before do + post product_items_path(product.id), params: item_params + post product_items_path(product2.id), params: item_params + end + it "will set cart in session to nil if valid cart purchase" do + expect(session[:cart_id]).wont_be_nil + + expect { + patch purchase_cart_path, params: cart_params + }.wont_change "Order.count" + + expect(session[:cart_id]).must_be_nil + end + + it "will set confirmation to true in session if valid cart purchase" do + expect(session[:confirmation]).must_be_nil + order_id = session[:cart_id] + + expect { + patch purchase_cart_path, params: cart_params + }.wont_change "Order.count" + + expect(session[:confirmation]).must_equal order_id + end + + it "will change order status from pending to paid if valid cart purchase" do + order = Order.find_by(id: session[:cart_id]) + expect(order.status).must_equal "pending" + + expect { + patch purchase_cart_path, params: cart_params + }.wont_change "Order.count" + order.reload + expect(order.status).must_equal "paid" + end + + it "will change product stock by amount bought if valid cart purchase " do + product_stock = product.stock + product2.stock + + expect { + patch purchase_cart_path, params: cart_params + }.wont_change "Order.count" + product.reload + product2.reload + expect(product_stock - 4).must_equal product.stock + product2.stock + end + + it "will have flash message and redirect to correct page if valid cart purchase" do + expect { + patch purchase_cart_path, params: cart_params + }.wont_change "Order.count" + + order = Order.find_by(shopper_address: "44550 mailing address") + expect(flash[:success]).must_equal "Purchase Successful" + must_respond_with :redirect + must_redirect_to order_confirmation_path(order) + end + + describe "checking out will an invalid cart" do + let(:product) { products(:product_1) } + let(:product2) { products(:product_2) } + let(:item_params) { { item: { quantity: 2 } } } + let(:cart_params) { + { order: { shopper_name: "susie", + shopper_email: "dalmation@susy.org", + shopper_address: "4455 mailing address", + cc_all: "234443434", + cc_exp: "03022020" } } + } + before do + product.update(stock: 1) + post product_items_path(product.id), params: item_params + post product_items_path(product2.id), params: item_params + end + + it "will have flash message and redirect to correct page if invalid cart purchase" do + expect { + patch purchase_cart_path, params: cart_params + }.wont_change "Order.count" + + expect(flash[:error]).must_equal "Unable to checkout cart" + expect(flash["Item exceeds available stock:"]).must_equal ["bucket of rain available stock is 1."] + must_respond_with :redirect + must_redirect_to cart_path + end + + it "will not change order status if invalid cart" do + order = Order.find_by(id: session[:cart_id]) + expect(order.status).must_equal "pending" + + expect { + patch purchase_cart_path, params: cart_params + }.wont_change "Order.count" + + expect(order.status).must_equal "pending" + end + + it "will not change product stock if invalid cart" do + product_stock = product.stock + product2.stock + + expect { + patch purchase_cart_path, params: cart_params + }.wont_change "Order.count" + + product.reload + product2.reload + expect(product_stock).must_equal product.stock + product2.stock + end + + it "will not change cart in session if invalid cart" do + expect(session[:cart_id]).wont_be_nil + expect { + patch purchase_cart_path, params: cart_params + }.wont_change "Order.count" + expect(session[:cart_id]).wont_be_nil + end + + it "will set not set confirmation in session if invalid cart purchase attempt" do + expect(session[:confirmation]).must_be_nil + + expect { + patch purchase_cart_path, params: cart_params + }.wont_change "Order.count" + + expect(session[:confirmation]).must_be_nil + end + 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..9e05272172 --- /dev/null +++ b/test/controllers/products_controller_test.rb @@ -0,0 +1,279 @@ +require "test_helper" + +describe ProductsController do + describe "index" do + it "successfully shows index" do + get products_path + + must_respond_with :success + end + + it "successfully shows index with no products" do + Item.destroy_all + Review.destroy_all + Product.destroy_all + + get products_path + + must_respond_with :success + end + end + + describe "show" do + it "can get a valid product" do + get product_path(products(:product_1).id) + + must_respond_with :success + end + + it "will give a redirect and give a flash notice for an invalid product" do + get product_path(-1) + + must_respond_with :redirect + expect(flash[:error]).must_equal "Unknown product" + end + end + + describe "new" do + describe "logged in merchant" do + it "succeeds" do + perform_login + get new_product_path + + must_respond_with :success + end + end + describe "not logged in" do + it "redirects user" do + get new_product_path + + must_respond_with :redirect + expect(flash[:error]).must_equal "You must be logged in to add a new product" + end + end + end + + describe "create" do + describe "logged in user" do + it "creates a product with valid data" do + merchant = perform_login + new_product = {product: {name: "Something amazing", price: 1000, stock: 4}} + expect { + post products_path, params: new_product + }.must_change "Product.count", 1 + + new_product_id = Product.find_by(name: "Something amazing").id + + expect(flash[:success]).must_equal "Product added successfully" + must_respond_with :redirect + must_redirect_to product_path(new_product_id) + end + + it "renders bad_request and does not update the DB for bogus data" do + merchant = perform_login + bad_prod_name = {product: {name: nil, price: 1000}} + + expect { + post products_path, params: bad_prod_name + }.wont_change "Product.count" + + must_respond_with :bad_request + expect(flash[:error]).must_include "Could not add new product" + expect(flash[:name]).must_include "can't be blank" + end + + describe "logged out user" do + it "cannot create a product with valid data" do + new_product = {product: {name: "Something amazing", price: 1000}} + expect { + post products_path, params: new_product + }.wont_change "Product.count" + + must_respond_with :bad_request + expect(flash[:error]).must_equal "You must be logged in to create a new product" + end + end + end + end + + describe "edit" do + describe "logged in merchant" do + it "succeeds" do + merchant = perform_login + + get edit_product_path(merchant.products.first.id) + + must_respond_with :success + end + + it "will redirect if given an invalid product id" do + merchant = perform_login + + get edit_product_path(-1) + + must_respond_with :redirect + must_redirect_to products_path + end + end + describe "not logged in" do + it "redirects user" do + merchant = merchants(:merchant_1) + # do not do login + get edit_product_path(merchant.products.first.id) + + must_respond_with :redirect + must_redirect_to products_path + expect(flash[:error]).must_equal "You must be logged in to edit a product" + end + end + end + + describe "update" do + describe "logged in merchant" do + it "will update an existing product" do + merchant = perform_login + + starter_input = { + name: "An intangible thing", + merchant_id: merchant.id, + description: "Its hard to hold onto", + photo_url: "a cute url", + stock: 100, + price: 999, + } + + product_to_update = Product.create(starter_input) + + test_input = { + "product": { + name: "A REALLY COOL intangible thing", + merchant_id: merchants(:merchant_1).id, + description: "Its hard to hold onto", + photo_url: "a cute url", + stock: 100, + price: 999, + }, + } + + expect { + patch product_path(product_to_update.id), params: test_input + }.wont_change "Product.count" + + must_respond_with :redirect + must_redirect_to product_path(product_to_update.id) + product_to_update.reload + expect(product_to_update.name).must_equal test_input[:product][:name] + expect(product_to_update.merchant_id).must_equal test_input[:product][:merchant_id] + expect(product_to_update.description).must_equal test_input[:product][:description] + expect(flash[:success]).must_equal "Product updated successfully" + end + + it "will not update an existing product if given bogus data" do + merchant = perform_login + product_to_update = products(:product_3) + + test_input = { + "product": { + name: nil, + merchant_id: merchant.id, + description: "nice stuff wooowza", + photo_url: "https://yeahforsure.org", + stock: 100, + price: 999, + }, + } + + expect { + patch product_path(product_to_update.id), params: test_input + }.wont_change "Product.count" + + must_respond_with :bad_request + expect(flash[:name]).must_include "can't be blank" + end + + it "can only update their own products" do + merchant = perform_login(merchants(:merchant_1)) + product_to_update = products(:product_1) #does not belong to merchant 1 + + test_input = { + "product": { + name: "A really nice new thing", + merchant_id: merchant.id, + description: "nice stuff wooowza", + photo_url: "https://yeahforsure.org", + stock: 100, + price: 999, + }, + } + + expect { + patch product_path(product_to_update.id), params: test_input + }.wont_change "Product.count" + + must_respond_with :redirect + must_redirect_to products_path + expect(flash[:error]).must_equal "You can only update your own products" + end + end + + describe "not logged in" do + # I think this is covered in the edit tests since users cannot get to the edit page if they are not logged in. Let me know if I need to test this. + end + end + + describe "toggle inactive" do + describe "logged in merchant" do + it "can change status for their products active from true to false and false to true" do + # Change from true to false + perform_login(merchants(:merchant_2)) + product = products(:product_1) + + patch toggle_inactive_path(product.id) + product.reload + + expect(product.active).must_equal false + must_respond_with :redirect + must_redirect_to current_merchant_path + expect(flash[:success]).must_equal "Product status changed successfully" + + # Change back from false back to true + patch toggle_inactive_path(product.id) + + product.reload + + expect(product.active).must_equal true + must_respond_with :redirect + must_redirect_to current_merchant_path + expect(flash[:success]).must_equal "Product status changed successfully" + end + + it "cannot change status of another merchants product" do + perform_login(merchants(:merchant_1)) #not the creator of product_1 + product = products(:product_1) + + patch toggle_inactive_path(product.id) + product.reload + + expect(product.active).must_equal true + must_respond_with :redirect + must_redirect_to current_merchant_path + expect(flash[:error]).must_equal "You may only change the status of your own products" + end + end + + describe "logged out" do + it "cannot change status from true to false" do + # do not perform login + product = products(:product_1) + + patch toggle_inactive_path(product.id) + product.reload + + expect(product.active).must_equal true + must_respond_with :redirect + must_redirect_to current_merchant_path + expect(flash[:error]).must_equal "You must be logged in to change the status of a product" + end + 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..ab697ef2a2 --- /dev/null +++ b/test/controllers/reviews_controller_test.rb @@ -0,0 +1,68 @@ +require "test_helper" + +describe ReviewsController do + describe "create" do + describe "not logged in user" do + it "will create a new review" do + review_param = { review: { rating: 3, + comment: "wow so great" } } + product = products(:product_1) + + expect { + post product_reviews_path(product), params: review_param + }.must_change "Review.count", 1 + + expect(flash[:success]).must_equal "Review Successfully Added" + + must_respond_with :redirect + must_redirect_to product_path(product) + end + + it "will not create a reiew if params are invalid" do + review_param = { review: { rating: 8, + comment: "wow so great" } } + product = products(:product_1) + + expect { + post product_reviews_path(product), params: review_param + }.must_change "Review.count", 0 + expect(flash[:error]).must_equal "Could Not Add Review" + expect(flash[:rating]).must_equal ["is not included in the list"] + + must_respond_with :bad_request + end + end + describe "as a merchant" do + before do + perform_login() + end + it "will create a new review of a product that does not belong to merchant" do + review_param = { review: { rating: 3, + comment: "wow so great" } } + product = products(:product_1) + + expect { + post product_reviews_path(product), params: review_param + }.must_change "Review.count", 1 + + expect(flash[:success]).must_equal "Review Successfully Added" + + must_respond_with :redirect + must_redirect_to product_path(product) + end + + it "a merchant cannot create a review their own product" do + review_param = { review: { rating: 3, + comment: "wow so great" } } + product = products(:product_3) + expect { + post product_reviews_path(product), params: review_param + }.must_change "Review.count", 0 + + expect(flash[:error]).must_equal "Merchants cannot review their own product." + + must_respond_with :bad_request + end + 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..4c0ed821ca --- /dev/null +++ b/test/fixtures/categories.yml @@ -0,0 +1,7 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +category_1: + name: majestic + +category_2: + name: happiness diff --git a/test/fixtures/files/.keep b/test/fixtures/files/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/fixtures/items.yml b/test/fixtures/items.yml new file mode 100644 index 0000000000..6e9749bd58 --- /dev/null +++ b/test/fixtures/items.yml @@ -0,0 +1,22 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +item_1: + quantity: 5 + order: order_1 + product: product_1 + +item_2: + quantity: 3 + order: order_1 + product: product_2 + +item_3: + quantity: 3 + order: order_2 + product: product_2 + +item_4: + quantity: 3 + order: order_3 + product: product_2 + diff --git a/test/fixtures/merchants.yml b/test/fixtures/merchants.yml new file mode 100644 index 0000000000..67673550dc --- /dev/null +++ b/test/fixtures/merchants.yml @@ -0,0 +1,20 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +merchant_1: + username: pickchu + email: pickhu@yahoo.com + uid: 2342 + provider: github + +merchant_2: + username: lemonlulu + email: lululumen@yahoo.com + uid: 5435 + provider: github + + +merchant_3: + username: leopoke + email: lhifi@yahoo.com + uid: 1234 + provider: github \ No newline at end of file diff --git a/test/fixtures/orders.yml b/test/fixtures/orders.yml new file mode 100644 index 0000000000..c558135d06 --- /dev/null +++ b/test/fixtures/orders.yml @@ -0,0 +1,29 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +order_1: + status: complete + shopper_name: susie + shopper_email: dalmation@susy.org + shopper_address: 4455 mailing address + cc_four: 3434 + cc_exp: 03022020 + +order_2: + status: paid + shopper_name: miranda + shopper_email: panda@hooo.com + shopper_address: 43244 tryagain address + cc_four: 2534 + cc_exp: 09092020 + +order_3: + status: pending + shopper_name: miranda + shopper_email: panda@hooo.com + shopper_address: 43244 tryagain address + cc_four: 2534 + cc_exp: 09092020 + + + + diff --git a/test/fixtures/products.yml b/test/fixtures/products.yml new file mode 100644 index 0000000000..756760d161 --- /dev/null +++ b/test/fixtures/products.yml @@ -0,0 +1,29 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +product_1: + name: bucket of rain + price: 1087 + description: just like it sounds you sad person + photo_url: https://notonmyblock.com + stock: 8 + active: true + merchant: merchant_2 + +product_2: + name: sushine and rainbows + price: 1722 + description: nice stuff wooowza + photo_url: https://yeahforsure.org + stock: 7 + active: true + merchant: merchant_2 + + +product_3: + name: nice things + price: 1722 + description: nice stuff wooowza + photo_url: https://yeahforsure.org + stock: 7 + active: true + merchant: merchant_1 diff --git a/test/fixtures/reviews.yml b/test/fixtures/reviews.yml new file mode 100644 index 0000000000..9df21fd3f4 --- /dev/null +++ b/test/fixtures/reviews.yml @@ -0,0 +1,11 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +review_1: + rating: 5 + comment: impressive liked it a lot it was supppper good + product: product_1 + +review_2: + rating: 2 + comment: not so awesome sad face + product: product_1 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..48f57e2e24 --- /dev/null +++ b/test/models/category_test.rb @@ -0,0 +1,40 @@ +require "test_helper" + +describe Category do + let(:category) { categories(:category_1) } + + it "must be valid" do + expect(category.valid?).must_equal true + end + + describe "validations" do + it "will not be valid if missing name" do + category.name = nil + expect(category.valid?).must_equal false + expect(category.errors.include?(:name)).must_equal true + expect(category.errors[:name]).must_equal ["can't be blank"] + end + + it "will not be valid if name not unique" do + category_dup = Category.new(name: category.name) + expect(category_dup.valid?).must_equal false + expect(category_dup.errors.include?(:name)).must_equal true + expect(category_dup.errors[:name]).must_equal ["has already been taken"] + end + end + + describe "relationships for products" do + it "can have 0 products" do + expect(category.products.count).must_equal 0 + end + + it "can have one or more products" do + category.products << products(:product_1) + category.products << products(:product_2) + expect(category.products.include?(products(:product_1))).must_equal true + expect(category.products.include?(products(:product_2))).must_equal true + expect(products(:product_1).categories.include?(category)).must_equal true + end + end +end + diff --git a/test/models/item_test.rb b/test/models/item_test.rb new file mode 100644 index 0000000000..3acd452e6d --- /dev/null +++ b/test/models/item_test.rb @@ -0,0 +1,118 @@ +require "test_helper" + +describe Item do + let(:item) { items(:item_1) } + let(:product) { products(:product_1) } + + it "must be valid" do + expect(item.valid?).must_equal true + end + + describe "validations" do + it "will not be valid if missing quantity" do + item.quantity = nil + expect(item.valid?).must_equal false + expect(item.errors.include?(:quantity)).must_equal true + expect(item.errors.messages[:quantity]).must_equal ["can't be blank", "is not a number"] + end + + it "will not be valid if quantity is zero" do + item.quantity = 0 + expect(item.valid?).must_equal false + expect(item.errors.include?(:quantity)).must_equal true + expect(item.errors.messages[:quantity]).must_equal ["must be greater than 0"] + end + + it "will not be valid if quantity is less than zero" do + item.quantity = -1 + expect(item.valid?).must_equal false + expect(item.errors.include?(:quantity)).must_equal true + expect(item.errors.messages[:quantity]).must_equal ["must be greater than 0"] + end + end + + describe "relationships" do + describe "relationship with an order" do + it "will belong to an order" do + expect(item.order).must_equal orders(:order_1) + end + end + + describe "relationship with an product" do + it "will belong to an product" do + expect(item.product).must_equal products(:product_1) + end + end + end + + describe "custom methods" do + describe " def subtotal" do + it "will calculate the subtotal of an item given a valid item and product" do + expect(item.subtotal).must_equal item.quantity * product.price / 100.0 + end + + it "will return nil if item is invalid" do + item = Item.new + expect(item.subtotal).must_be_nil + end + + it "will return nil if product assoctiated with item is invalid" do + item.product = Product.new + expect(item.subtotal).must_be_nil + end + end + describe " def available_for_purchase?" do + it "will return true if the quantity requested is less than stock" do + expect(item.available_for_purchase?).must_equal true + end + + it "will return true if the quantity requested equal to stock" do + item.update(quantity: product.stock) + expect(item.available_for_purchase?).must_equal true + end + + it "will return false if the quantity requested is greater than stock" do + item.update(quantity: product.stock + 1) + expect(item.available_for_purchase?).must_equal false + end + + it "will return nil if item is invalid" do + item = Item.new + expect(item.available_for_purchase?).must_be_nil + end + + it "will return nil if product assoctiated with item is invalid" do + item.product = Product.new + expect(item.available_for_purchase?).must_be_nil + end + end + + describe " def purchase" do + # tests for quantity decrease/not decreasing + # done in product model test, custom methods section. + it "will return true if the quantity requested is less than stock" do + expect(item.purchase).must_equal true + end + + it "will return true if the quantity requested equal to stock" do + item.update(quantity: product.stock) + expect(item.purchase).must_equal true + end + + it "will return false if the quantity requested is greater than stock" do + item.update(quantity: product.stock + 1) + expect(item.purchase).must_equal false + end + + it "will return nil if item is invalid" do + item = Item.new + expect(item.purchase).must_be_nil + end + + it "will return nil if product assoctiated with item is invalid" do + item.product = Product.new + expect(item.purchase).must_be_nil + end + end + end +end diff --git a/test/models/merchant_test.rb b/test/models/merchant_test.rb new file mode 100644 index 0000000000..a4a34937db --- /dev/null +++ b/test/models/merchant_test.rb @@ -0,0 +1,102 @@ +require "test_helper" +require "pry" + +describe Merchant do + let(:merchant) { merchants(:merchant_1) } + + it "must be valid" do + expect(merchant.valid?).must_equal true + end + + describe "validations" do + it "will not be valid if missing username" do + merchant.username = nil + expect(merchant.valid?).must_equal false + expect(merchant.errors.include?(:username)).must_equal true + expect(merchant.errors[:username]).must_equal ["can't be blank"] + end + + it "will not be valid if username is not unique" do + merchant_dup = Merchant.new(username: merchant.username, + email: "duplicate@adda.com", + uid: "4527", + provider: "github") + expect(merchant_dup.valid?).must_equal false + expect(merchant_dup.errors.include?(:username)).must_equal true + expect(merchant_dup.errors[:username]).must_equal ["has already been taken"] + end + + it "will not be valid if missing email" do + merchant.email = nil + expect(merchant.valid?).must_equal false + expect(merchant.errors.include?(:email)).must_equal true + expect(merchant.errors[:email]).must_equal ["can't be blank"] + end + + it "will not be valid if email is not unique" do + merchant_dup = Merchant.new(username: "gilfoyle", + email: merchant.email, + uid: "4527", + provider: "github") + expect(merchant_dup.valid?).must_equal false + expect(merchant_dup.errors.include?(:email)).must_equal true + expect(merchant_dup.errors[:email]).must_equal ["has already been taken"] + end + + it "will not be valid if missing uid" do + merchant.uid = nil + expect(merchant.valid?).must_equal false + expect(merchant.errors.include?(:uid)).must_equal true + expect(merchant.errors[:uid]).must_equal ["can't be blank"] + end + + it "will not be valid if missing provider" do + merchant.provider = nil + expect(merchant.valid?).must_equal false + expect(merchant.errors.include?(:provider)).must_equal true + expect(merchant.errors[:provider]).must_equal ["can't be blank"] + end + end + + describe "relationship with product" do + it "can have 0 products" do + merchant.products.destroy_all + expect(merchant.products.count).must_equal 0 + end + + it "can have 1 or more products" do + merchant = merchants(:merchant_2) + expect(merchant.products.count).must_equal 2 + end + + it "can shovel product to create relationship" do + product = Product.new(name: "penny", price: 1200, stock: 4) + expect(product.valid?).must_equal false + merchant.products << product + expect(product.valid?).must_equal true + expect(merchant.products.include?(product)).must_equal true + end + end + + describe "custom method build_from_github" do + it "build auth hash" do + # this_merchant = merchants(:merchant_1) + + new_auth_hash = { + provider: "github", + uid: "543", + info: { + email: "jackie@me.net", + nickname: "EllesMom", + }, + } + # binding.pry + new_merch = Merchant.build_from_github(new_auth_hash) + + new_merch.provider.must_equal "github" + new_merch.uid.must_equal new_auth_hash[:uid] + new_merch.email.must_equal new_auth_hash[:info][:email] + new_merch.username.must_equal new_auth_hash[:info][:nickname] + end + end +end diff --git a/test/models/order_test.rb b/test/models/order_test.rb new file mode 100644 index 0000000000..c8c9c4a566 --- /dev/null +++ b/test/models/order_test.rb @@ -0,0 +1,190 @@ +require "test_helper" + +describe Order do + let(:order) { orders(:order_1) } + + it "must be valid" do + expect(order.valid?).must_equal true + end + + describe "validations" do + it "has a default value of 'pending'" do + order = Order.new + expect(order.status).must_equal "pending" + end + + it "will be valid if given status of [\"pending\", \"paid\", \"complete\", \"cancelled\"]" do + ["pending", "paid", "complete", "cancelled"].each do |status| + order.update(status: status) + expect(order.valid?).must_equal true + end + end + + it "will not be valid if status is different from above" do + ["penddding", "paiad", "compledte", "cancnelled"].each do |status| + order.update(status: status) + expect(order.valid?).must_equal false + end + end + + describe "relationship with an order" do + it "can have 0 items" do + Item.destroy_all + expect(order.items.count).must_equal 0 + end + + it "can have 1 or more items" do + expect(order.items.count).must_equal 2 + end + end + + it "can shovel items to create a relationship" do + product = products(:product_1) + item = Item.new(quantity: 3, product: product) + expect(item.valid?).must_equal false + order.items << item + expect(item.valid?).must_equal true + expect(order.items.include?(item)).must_equal true + end + end + describe "custom methods" do + describe " def total" do + it "will calculate total cost of cart" do + total_cost = items(:item_1).quantity * items(:item_1).product.price / 100.0 + total_cost += items(:item_2).quantity * items(:item_2).product.price / 100.0 + expect(order.total).must_be_close_to total_cost + end + + it "will return nil if invalid item in cart" do + order.items << Item.new(order_id: order.id) + expect(order.total).must_be_nil + end + + it "will return nil if item has an invalid item in cart" do + items(:item_3).product = Product.new + order.items << items(:item_3) + expect(order.total).must_be_nil + end + end + describe "cart errors" do + it "will return [] if all items are available for purchase" do + expect(order.cart_errors).must_equal [] + end + + it "will return items in array that are not available for purchase" do + items(:item_3).quantity = 1000 + order.items << items(:item_3) + items(:item_1).update(quantity: 2000) + expect(order.cart_errors.sort!).must_equal [items(:item_1), items(:item_3)].sort! + end + + it "will return nil if item(s) are not valid" do + order.items << Item.new(order_id: order.id) + expect(order.cart_errors).must_be_nil + end + + it "will return nil if an items product is not valid" do + items(:item_3).product = Product.new + order.items << items(:item_3) + expect(order.cart_errors).must_be_nil + end + end + describe " def cart_checkout" do + # tests for quantity decrease/not decreasing + # done in product model test, custom methods section. + it "will return true if items and products are valid" do + expect(order.cart_checkout).must_equal true + end + + it "will return nil if item(s) are not valid" do + order.items << Item.new(order_id: order.id) + expect(order.cart_checkout).must_be_nil + end + it "will return nil if an items product is not valid" do + items(:item_3).product = Product.new + order.items << items(:item_3) + expect(order.cart_checkout).must_be_nil + end + end + + describe "class methods " do + let(:merchant) { merchants(:merchant_2) } + let(:item_hash) { Order.find_merchant_order_items(merchant) } + describe "self.find_merchant_order_items(merchant, items_hash: {\"paid\" => {}, \"complete\" => {} })" do + it "will return a nested hash structure thatsstores merchant order items: + key is order status and value is a hash with order_id as key and array of item objects as value:" do + expect(item_hash.to_s).must_equal ({ "paid" => { "615724322" => [Item.find(477087474)] }, "complete" => { "1035625630" => [Item.find(845260767), Item.find(728299111)] } }).to_s + end + + it "will return empty hash for values with no orders in an order.status" do + expect(Order.find_merchant_order_items(merchants(:merchant_3)).to_s).must_equal ({ "paid" => {}, "complete" => {} }).to_s + end + + it "will return hash with additional statuses and add orders to them passed as params" do + expect(Order.find_merchant_order_items(merchant, items_hash: { "paid" => {}, "complete" => {}, "pending" => {} }).to_s).must_equal ({ "paid" => { "615724322" => [Item.find(477087474)] }, "complete" => { "1035625630" => [Item.find(845260767), Item.find(728299111)] }, "pending" => { "330565047" => [Item.find(34296661)] } }).to_s + end + + # NOT 100% SURE IF THESE TESTS ARE ACTUALLY TESTING WHAT THEY ARE SUPPOSED TO. + # ALSO LOTS OF CHECKS AND VAILDATIONS, SO THESE SCENARIOS WOULD NOT HAPPEN. DO # THESE EVEN HAVE TO BE TESTED? + + # it "invalid order will not be added to hash" do + # order = Order.new(status: "noneya") + # item = Item.new(product: products(:product_2), order: order, quantity: 4) + # expect(item.valid?).must_equal true + # expect(order.valid?).must_equal false + # expect(item_hash.to_s).must_equal ({ "paid" => { "615724322" => [Item.find(477087474)] }, "complete" => { "1035625630" => [Item.find(845260767), Item.find(728299111)] } }).to_s + # end + + # it "invalid item will not be added to hash" do + # items(:item_2).update(quantity: nil) + # expect(items(:item_2).valid?).must_equal false + # expect(item_hash.to_s).must_equal ({ "paid" => { "615724322" => [Item.find(477087474)] }, "complete" => { "1035625630" => [Item.find(845260767)] } }).to_s + # end + + it "invalid order will not be added to hash even if supplied the status in item_hash" do + order = Order.new(status: "noneya") + item = Item.new(product: products(:product_2), order: order, quantity: 4) + expect(item.valid?).must_equal true + expect(order.valid?).must_equal false + expect(Order.find_merchant_order_items(merchant, items_hash: { "paid" => {}, "complete" => {}, "noneya" => {} }).to_s).must_equal ({ "paid" => { "615724322" => [Item.find(477087474)] }, "complete" => { "1035625630" => [Item.find(845260767), Item.find(728299111)] }, "noneya" => {} }).to_s + end + end + + describe " def self.status_revenue(item_status)" do + let(:revenue_completed) { Order.status_revenue(item_hash["complete"]) } + + it "will calculate the sum of all the orders of a given satus" do + expect(revenue_completed).must_be_close_to Item.find(845260767).subtotal + Item.find(728299111).subtotal + end + + it "will return 0 if no items for a given status" do + Item.destroy_all + expect(revenue_completed).must_equal 0 + end + + it "will return 0 if asked for revenue of status that is not in hash" do + expect(Order.status_revenue(item_hash["not a real status"])).must_equal 0 + end + end + + describe "def self.status_count_orders(item_status)" do + it "will count all the orders of a given status " do + orders(:order_2).update(status: "complete") + expect(orders(:order_2).valid?).must_equal true + + expect(Order.status_count_orders(item_hash["complete"])).must_equal 2 + end + + it "will return 0 if no orders of a given status" do + Item.destroy_all + Order.destroy_all + expect(Order.status_count_orders(item_hash["complete"])).must_equal 0 + end + + it "will return 0 if given an empty hash (ie status not in items_hash)" do + expect(Order.status_count_orders(item_hash["no status"])).must_equal 0 + end + end + end + end +end diff --git a/test/models/product_test.rb b/test/models/product_test.rb new file mode 100644 index 0000000000..270ba4b27b --- /dev/null +++ b/test/models/product_test.rb @@ -0,0 +1,143 @@ +require "test_helper" + +describe Product do + let(:product) { products(:product_1) } + + it "must be valid" do + expect(product.valid?).must_equal true + end + + describe "validations" do + it "will not be valid if missing name" do + product.name = nil + expect(product.valid?).must_equal false + expect(product.errors.include?(:name)).must_equal true + expect(product.errors.messages[:name]).must_equal ["can't be blank"] + end + + it "will not be valid if name is not unique" do + product_dupe = Product.new(name: product.name, + price: 222, + merchant: merchants(:merchant_1)) + expect(product_dupe.valid?).must_equal false + expect(product_dupe.errors.include?(:name)).must_equal true + expect(product_dupe.errors.messages[:name]).must_equal ["has already been taken"] + end + + it "will not be valid if missing price" do + product.price = nil + expect(product.valid?).must_equal false + expect(product.errors.include?(:price)).must_equal true + expect(product.errors.messages[:price]).must_equal ["can't be blank", "is not a number"] + end + + it "will not be valid if price is zero" do + product.price = 0 + expect(product.valid?).must_equal false + expect(product.errors.include?(:price)).must_equal true + expect(product.errors.messages[:price]).must_equal ["must be greater than 0"] + end + + it "will not be valid if price is less than zero" do + product.price = -1 + expect(product.valid?).must_equal false + expect(product.errors.include?(:price)).must_equal true + expect(product.errors.messages[:price]).must_equal ["must be greater than 0"] + end + + it "will not be valid if missing stock" do + product.stock = nil + expect(product.valid?).must_equal false + expect(product.errors.include?(:stock)).must_equal true + expect(product.errors.messages[:stock]).must_equal ["can't be blank", "is not a number"] + end + + it "will be valid if stock is 0" do + product.stock = 0 + expect(product.valid?).must_equal true + end + + it "will not be valid if stock is less than zero" do + product.stock = -2 + expect(product.valid?).must_equal false + expect(product.errors.include?(:stock)).must_equal true + expect(product.errors.messages[:stock]).must_equal ["must be greater than -1"] + end + end + + describe "relationships" do + describe "relationship with merchant" do + it "will belong to a merchant" do + expect(product.merchant).must_equal merchants(:merchant_2) + end + end + + describe "relationship with categories" do + it "can have 0 categories" do + expect(product.categories).must_equal [] + end + + it "can have 1 or many categories" do + product.categories << categories(:category_1) + product.categories << categories(:category_2) + expect(product.categories.include?(categories(:category_1))).must_equal true + expect(product.categories.include?(categories(:category_2))).must_equal true + expect(categories(:category_2).products.include?(product)).must_equal true + end + end + + describe "relationship with reviews" do + it "can have 0 reviews" do + product = products(:product_2) + expect(product.reviews).must_equal [] + end + + it "can have 1 or many reviews" do + expect(product.reviews.include?(reviews(:review_1))).must_equal true + expect(product.reviews.include?(reviews(:review_2))).must_equal true + expect(reviews(:review_2).product).must_equal product + end + end + + describe "relationship with items" do + it "can have 0 items" do + product = products(:product_3) + expect(product.items).must_equal [] + end + + it "can have 1 or many items" do + product = products(:product_2) + expect(product.items.include?(items(:item_2))).must_equal true + expect(product.items.include?(items(:item_3))).must_equal true + expect(items(:item_3).product).must_equal product + end + end + end + + describe "custom methods" do + let(:initial_stock) { product.stock } + describe " def decrease_stock(quantity)" do + it "will decrease the stock of a product by given quantity and return true if valid" do + quantity = 2 + expect(initial_stock).must_equal product.stock + expect(product.decrease_stock(quantity)).must_equal true + expect(initial_stock).wont_equal product.stock + expect(product.stock).must_equal initial_stock - quantity + end + + it "will return false if quantity exceeds stock and not decrease stock" do + quantity = 10 + expect(initial_stock).must_equal product.stock + expect(product.decrease_stock(quantity)).must_equal false + product.reload + expect(initial_stock).must_equal product.stock + end + + it "will return nil if called on invalid product" do + product = Product.new + quantity = 1 + expect(product.decrease_stock(quantity)).must_be_nil + end + end + end +end diff --git a/test/models/review_test.rb b/test/models/review_test.rb new file mode 100644 index 0000000000..2461b0ffc3 --- /dev/null +++ b/test/models/review_test.rb @@ -0,0 +1,38 @@ +require "test_helper" + +describe Review do + let(:review) { reviews(:review_1) } + + it "must be valid" do + expect(review.valid?).must_equal true + end + + describe "validations" do + it "will not be valid if missing rating" do + review.rating = nil + expect(review.valid?).must_equal false + expect(review.errors.include?(:rating)).must_equal true + expect(review.errors[:rating]).must_equal ["can't be blank", "is not included in the list"] + end + + it "will not be valid if rating is less than 1" do + review.rating = -1 + expect(review.valid?).must_equal false + expect(review.errors.include?(:rating)).must_equal true + expect(review.errors[:rating]).must_equal ["is not included in the list"] + end + + it "will not be valid if rating is greater than 5" do + review.rating = 6 + expect(review.valid?).must_equal false + expect(review.errors.include?(:rating)).must_equal true + expect(review.errors[:rating]).must_equal ["is not included in the list"] + end + end + + describe "relationship with product" do + it "belongs to a product" do + expect(review.product).must_equal products(:product_1) + 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..1c0925a944 --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,48 @@ +require "simplecov" +SimpleCov.start "rails" +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 + OmniAuth.config.test_mode = true + + def mock_auth_hash(merchant) + return { + provider: merchant.provider, + uid: merchant.uid, + info: { + email: merchant.email, + nickname: merchant.username, + }, + } + end + + def perform_login(merchant = nil) + merchant ||= merchants(:merchant_1) + + OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new(mock_auth_hash(merchant)) + get auth_callback_path(:github) + + must_respond_with :redirect + must_redirect_to root_path + return merchant + 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