diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..e7299cf62b --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +# See https://help.github.com/articles/ignoring-files for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile '~/.gitignore_global' + +# Ignore bundler config. +/.bundle +coverage/* + +# 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 + +# Ignore .env file +.env diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000000..e54d447e8f --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +ruby-2.4.1 \ No newline at end of file diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000000..c6e92c8cef --- /dev/null +++ b/Gemfile @@ -0,0 +1,86 @@ +source 'https://rubygems.org' +git_source(:github) { |repo| "https://github.com/#{repo}.git" } + +ruby '2.4.1' + +# Bundle edge Rails instead: gem 'rails', github: 'rails/rails' +gem 'rails', '~> 5.2.1' +# Use postgresql as the database for Active Record +gem 'pg', '>= 0.18', '< 2.0' +# Use Puma as the app server +gem 'puma', '~> 3.11' +# Use SCSS for stylesheets +gem 'sass-rails', '~> 5.0' +# Use Uglifier as compressor for JavaScript assets +gem 'uglifier', '>= 1.3.0' +# See https://github.com/rails/execjs#readme for more supported runtimes +# gem 'mini_racer', platforms: :ruby + +# Use CoffeeScript for .coffee assets and views +# gem 'coffee-rails', '~> 4.2' +# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks +gem 'turbolinks', '~> 5' +# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder +gem 'jbuilder', '~> 2.5' +# Use Redis adapter to run Action Cable in production +# gem 'redis', '~> 4.0' +# Use ActiveModel has_secure_password +# gem 'bcrypt', '~> 3.1.7' + +# Use ActiveStorage variant +# gem 'mini_magick', '~> 4.8' + +# Use Capistrano for deployment +# gem 'capistrano-rails', group: :development + +# Reduces boot times through caching; required in config/boot.rb +gem 'bootsnap', '>= 1.1.0', require: false +gem 'omniauth' +gem 'omniauth-github' + +group :development, :test do + # Call 'byebug' anywhere in the code to stop execution and get a debugger console + gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] +end + +group :development do + # Access an interactive console on exception pages or by calling 'console' anywhere in the code. + gem 'web-console', '>= 3.3.0' + gem 'listen', '>= 3.0.5', '< 3.2' + # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring + gem 'spring' + gem 'spring-watcher-listen', '~> 2.0.0' + gem 'dotenv-rails' +end + +group :test do + # Adds support for Capybara system testing and selenium driver + gem 'capybara', '>= 2.15' + gem 'selenium-webdriver' + # Easy installation and use of chromedriver to run system tests with Chrome + gem 'chromedriver-helper' + gem 'simplecov', require: false +end + +# Windows does not include zoneinfo files, so bundle the tzinfo-data gem +gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] + +gem 'jquery-rails' +gem 'jquery-turbolinks' +gem 'bootstrap', '~> 4.1.3' +group :development, :test do + gem 'pry-rails' +end + +group :development do + gem 'better_errors' + gem 'binding_of_caller' + gem 'guard' + gem 'guard-minitest' +end + +group :test do + gem 'minitest-rails' + gem 'minitest-reporters' + gem 'simplecov', require: false +end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000000..5b12ef51ab --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,313 @@ +GEM + remote: https://rubygems.org/ + specs: + actioncable (5.2.1) + actionpack (= 5.2.1) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + actionmailer (5.2.1) + actionpack (= 5.2.1) + actionview (= 5.2.1) + activejob (= 5.2.1) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 2.0) + actionpack (5.2.1) + actionview (= 5.2.1) + activesupport (= 5.2.1) + rack (~> 2.0) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (5.2.1) + activesupport (= 5.2.1) + builder (~> 3.1) + erubi (~> 1.4) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.3) + activejob (5.2.1) + activesupport (= 5.2.1) + globalid (>= 0.3.6) + activemodel (5.2.1) + activesupport (= 5.2.1) + activerecord (5.2.1) + activemodel (= 5.2.1) + activesupport (= 5.2.1) + arel (>= 9.0) + activestorage (5.2.1) + actionpack (= 5.2.1) + activerecord (= 5.2.1) + marcel (~> 0.3.1) + activesupport (5.2.1) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) + minitest (~> 5.1) + tzinfo (~> 1.1) + addressable (2.5.2) + public_suffix (>= 2.0.2, < 4.0) + ansi (1.5.0) + archive-zip (0.11.0) + io-like (~> 0.3.0) + arel (9.0.0) + autoprefixer-rails (9.2.1) + execjs + better_errors (2.5.0) + coderay (>= 1.0.0) + erubi (>= 1.0.0) + rack (>= 0.9.0) + bindex (0.5.0) + binding_of_caller (0.8.0) + debug_inspector (>= 0.0.1) + bootsnap (1.3.2) + msgpack (~> 1.0) + bootstrap (4.1.3) + autoprefixer-rails (>= 6.0.3) + popper_js (>= 1.12.9, < 2) + sass (>= 3.5.2) + builder (3.2.3) + byebug (10.0.2) + capybara (3.9.0) + addressable + mini_mime (>= 0.1.3) + nokogiri (~> 1.8) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + xpath (~> 3.1) + childprocess (0.9.0) + ffi (~> 1.0, >= 1.0.11) + chromedriver-helper (2.1.0) + archive-zip (~> 0.10) + nokogiri (~> 1.8) + coderay (1.1.2) + concurrent-ruby (1.0.5) + crass (1.0.4) + debug_inspector (0.0.3) + docile (1.3.1) + dotenv (2.5.0) + dotenv-rails (2.5.0) + dotenv (= 2.5.0) + railties (>= 3.2, < 6.0) + erubi (1.7.1) + execjs (2.7.0) + faraday (0.15.3) + multipart-post (>= 1.2, < 3) + ffi (1.9.25) + formatador (0.2.5) + globalid (0.4.1) + activesupport (>= 4.2.0) + guard (2.14.2) + formatador (>= 0.2.4) + listen (>= 2.7, < 4.0) + lumberjack (>= 1.0.12, < 2.0) + nenv (~> 0.1) + notiffany (~> 0.0) + pry (>= 0.9.12) + shellany (~> 0.0) + thor (>= 0.18.1) + guard-compat (1.2.1) + guard-minitest (2.4.6) + guard-compat (~> 1.2) + minitest (>= 3.0) + hashie (3.5.7) + i18n (1.1.1) + concurrent-ruby (~> 1.0) + io-like (0.3.0) + jbuilder (2.7.0) + activesupport (>= 4.2.0) + multi_json (>= 1.2) + jquery-rails (4.3.3) + rails-dom-testing (>= 1, < 3) + railties (>= 4.2.0) + thor (>= 0.14, < 2.0) + jquery-turbolinks (2.1.0) + railties (>= 3.1.0) + turbolinks + json (2.1.0) + jwt (2.1.0) + listen (3.1.5) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + ruby_dep (~> 1.2) + loofah (2.2.2) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + lumberjack (1.0.13) + mail (2.7.1) + mini_mime (>= 0.1.1) + marcel (0.3.3) + mimemagic (~> 0.3.2) + method_source (0.9.0) + mimemagic (0.3.2) + mini_mime (1.0.1) + mini_portile2 (2.3.0) + minitest (5.11.3) + minitest-rails (3.0.0) + minitest (~> 5.8) + railties (~> 5.0) + minitest-reporters (1.3.5) + ansi + builder + minitest (>= 5.0) + ruby-progressbar + msgpack (1.2.4) + multi_json (1.13.1) + multi_xml (0.6.0) + multipart-post (2.0.0) + nenv (0.3.0) + nio4r (2.3.1) + nokogiri (1.8.5) + mini_portile2 (~> 2.3.0) + notiffany (0.1.1) + nenv (~> 0.1) + shellany (~> 0.0) + oauth2 (1.4.1) + faraday (>= 0.8, < 0.16.0) + jwt (>= 1.0, < 3.0) + multi_json (~> 1.3) + multi_xml (~> 0.5) + rack (>= 1.2, < 3) + omniauth (1.8.1) + hashie (>= 3.4.6, < 3.6.0) + rack (>= 1.6.2, < 3) + omniauth-github (1.3.0) + omniauth (~> 1.5) + omniauth-oauth2 (>= 1.4.0, < 2.0) + omniauth-oauth2 (1.5.0) + oauth2 (~> 1.1) + omniauth (~> 1.2) + pg (1.1.3) + popper_js (1.14.3) + pry (0.11.3) + coderay (~> 1.1.0) + method_source (~> 0.9.0) + pry-rails (0.3.6) + pry (>= 0.10.4) + public_suffix (3.0.3) + puma (3.12.0) + rack (2.0.5) + rack-test (1.1.0) + rack (>= 1.0, < 3) + rails (5.2.1) + actioncable (= 5.2.1) + actionmailer (= 5.2.1) + actionpack (= 5.2.1) + actionview (= 5.2.1) + activejob (= 5.2.1) + activemodel (= 5.2.1) + activerecord (= 5.2.1) + activestorage (= 5.2.1) + activesupport (= 5.2.1) + bundler (>= 1.3.0) + railties (= 5.2.1) + sprockets-rails (>= 2.0.0) + rails-dom-testing (2.0.3) + activesupport (>= 4.2.0) + nokogiri (>= 1.6) + rails-html-sanitizer (1.0.4) + loofah (~> 2.2, >= 2.2.2) + railties (5.2.1) + actionpack (= 5.2.1) + activesupport (= 5.2.1) + method_source + rake (>= 0.8.7) + thor (>= 0.19.0, < 2.0) + rake (12.3.1) + rb-fsevent (0.10.3) + rb-inotify (0.9.10) + ffi (>= 0.5.0, < 2) + ruby-progressbar (1.10.0) + ruby_dep (1.5.0) + rubyzip (1.2.2) + sass (3.6.0) + sass-listen (~> 4.0.0) + sass-listen (4.0.0) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + sass-rails (5.0.7) + railties (>= 4.0.0, < 6) + sass (~> 3.1) + sprockets (>= 2.8, < 4.0) + sprockets-rails (>= 2.0, < 4.0) + tilt (>= 1.1, < 3) + selenium-webdriver (3.14.1) + childprocess (~> 0.5) + rubyzip (~> 1.2, >= 1.2.2) + shellany (0.0.1) + simplecov (0.16.1) + docile (~> 1.1) + json (>= 1.8, < 3) + simplecov-html (~> 0.10.0) + simplecov-html (0.10.2) + spring (2.0.2) + activesupport (>= 4.2) + spring-watcher-listen (2.0.1) + listen (>= 2.7, < 4.0) + spring (>= 1.2, < 3.0) + sprockets (3.7.2) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-rails (3.2.1) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) + thor (0.20.0) + thread_safe (0.3.6) + tilt (2.0.8) + turbolinks (5.2.0) + turbolinks-source (~> 5.2) + turbolinks-source (5.2.0) + tzinfo (1.2.5) + thread_safe (~> 0.1) + uglifier (4.1.19) + execjs (>= 0.3.0, < 3) + web-console (3.7.0) + actionview (>= 5.0) + activemodel (>= 5.0) + bindex (>= 0.4.0) + railties (>= 5.0) + websocket-driver (0.7.0) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.3) + xpath (3.2.0) + nokogiri (~> 1.8) + +PLATFORMS + ruby + +DEPENDENCIES + better_errors + binding_of_caller + bootsnap (>= 1.1.0) + bootstrap (~> 4.1.3) + byebug + capybara (>= 2.15) + chromedriver-helper + dotenv-rails + guard + guard-minitest + jbuilder (~> 2.5) + jquery-rails + jquery-turbolinks + listen (>= 3.0.5, < 3.2) + minitest-rails + minitest-reporters + omniauth + omniauth-github + pg (>= 0.18, < 2.0) + pry-rails + puma (~> 3.11) + rails (~> 5.2.1) + sass-rails (~> 5.0) + selenium-webdriver + simplecov + spring + spring-watcher-listen (~> 2.0.0) + turbolinks (~> 5) + tzinfo-data + uglifier (>= 1.3.0) + web-console (>= 3.3.0) + +RUBY VERSION + ruby 2.4.1p111 + +BUNDLED WITH + 1.16.4 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 1864d2b973..7db80e4ca1 100644 --- a/README.md +++ b/README.md @@ -1,240 +1,24 @@ -# bEtsy -[Much like other e-commerce platforms](https://www.etsy.com/), your team will make an online store where a wide variety of products can be listed and sold by any user. This project focuses on reinforcing the major components of Rails, model validations, testing, and more complex logic such as user authentication. +# README -This is a [Stage 3](https://github.com/Ada-Developers-Academy/pedagogy/blob/master/rule-of-three.md) project requiring you to expand upon what you have learned in class. +This README would normally document whatever steps are necessary to get the +application up and running. -Due date: EOD Friday October 26 +Things you may want to cover: -## Project Learning Goals -- Core comprehension of: - - Routes - - Controllers - - Models - - Views -- User based application logic -- User authentication -- Testing on models and controllers -- Agile practices -- Feature branch management with Git -- Group project ownership +* Ruby version -## Guidelines -- Groups of three or four will collaborate in pairs or individually and will report to their assigned Project Manager (one of the instructors) -- Use a task manager like [Trello](http://trello.com) to track your team's efforts -- Build a logical user-flow that moves across multiple controllers and models -- Use HTML/CSS and Bootstap or another CSS framework to style your website +* System dependencies -## Getting Started -1. As a group decide on an app name (this may help lead the aesthetic) -1. As a group decide on a team name (this will amuse your instructors) -1. Have one person on your team fork/clone the project master as per usual - 1. Create a new rails app using `rails new .` - 1. Add all other team members as collaborators - 1. Each team member should clone the repo to their computer -1. Figure out your workflow for the project, re: Git and Task management - 1. Do you want to use git branches? Pull requests? - 1. Determine who will be the Stand Up Leader and Task Leader for the first week -1. Create a Trello board to use as a Kanban board and ensure that all team members and instructors have access -1. Review the User Stories below and create Trello tasks to represent them -1. Slack your team name, app name, and link to your Trello board to your Project Manager +* Configuration -## Expectations -Build an online system for listing, selling, reviewing, and buying a wide variety of products listed by multiple merchants. +* Database creation -### General Requirements -- Unit tests and/or specs for - - Models - - Controllers -- Test code coverage (using SimpleCov - remember me!) - - 90% for all controller and model classes +* Database initialization -### Prioritization +* How to run the test suite -This project contains many user stories, some of which are more relevant to our in-class learning goals than others. Here is a rough categorization of how important various features are: +* Services (job queues, cache servers, search engines, etc.) -#### High Priority +* Deployment instructions -- Browsing products -- Using the shopping cart (adding items, checking out) -- Authentication as a merchant -- CRUD on products, including authorization rules -- Model testing, particularly for any custom logic -- Controller testing - -#### Medium Priority - -- Product categories -- Basic validations (things that will break the site) -- Tracking product inventory -- Order fulfillment - -#### Low Priority - -- Reviews and ratings -- Comprehensive validations, particularly around submitting an order - -### User Stories -#### Guest User (Unauthenticated) -As a guest to the website (not signed in) I **can**: - -- Browse all products -- Browse products by category -- Browse products by merchant (users) -- View any individual product with additional details -- Leave a review for a product providing: - - A text review - - A rating out of 5 -- Add in-stock products to my cart -- Remove products from my cart -- Change the quantity of an existing product in my cart -- Purchase the items in my cart, providing: - - Email Address - - Mailing Address - - Name on credit card - - Credit card number - - Credit cart expiration - - Credit Card CVV (security code) - - Billing zip code -- Purchasing an order makes the following changes: - - Reduces the number of inventory for each product - - Changes the order state from "pending" to "paid" - - Clears the current cart -- After purchasing an order, I can view a confirmation screen including: - - Each item in the order with a quantity and line-item subtotal - - A link to the item description page - - Order total price - - Date/time the order was placed - - The current status of the order -- Sign up to be a merchant using OAuth - - Every merchant must have a username -- Sign in to my merchant account using OAuth - -As a guest I **cannot**: - -- Add products to the cart that are out of stock -- View any link or page to manage any products -- View any of the account pages - -#### Authenticated Users -As a signed-in user, I **can**: - -- Do everything a guest user can do except for sign up and sign in -- Sign out -- Create new categories (categories are shared between all merchants) -- Create a new product providing: - - name - - description - - price - - photo URL - - stock -- Assign my products to any number of categories -- Retire a product from being sold, which hides it from browsing -- View an account page to edit/update my existing products -- View an account page showing my order fulfillment -- On the order fulfillment page: - - Total Revenue - - Total Revenue by status - - Total number of orders by status - - Filter orders displayed by status - - Link to each individual order - - A list of orders including at least one of my products: - - Each order item sold by me with a quantity and line-item subtotal - - A link to the item description page - - DateTime the order was placed - - Link to transition the order item to marked as shipped - - The current status of the order ("pending", "paid", "complete", "cancelled") -- View an individual order to see the user's: - - Name - - Email address - - Mailing address - - Last four digits of their credit card - - Credit card expiration date - -As a signed-in user, I **cannot**: - -- Review my own products -- View order items from a shared order that belong to another merchant -- View another user's private data (i.e. order fulfillment or product management) - -### Validations -Many of our models will have attributes that are required for our application to use and display data consistently. Each model will have attributes with requirements for a valid record. The requirements are summarized below: - -#### Merchant -- Username must be present -- Username must be unique -- Email Address must be present -- Email Address must be unique - -#### Product -- Name must be present -- Name must be unique -- Price must be present -- Price must be a number -- Price must be greater than 0 -- Product must belong to a User - -#### Order -- An Order must have one or more Order Items - -#### OrderItem -- Must belong to a Product -- Must belong to an Order -- Quantity must be present -- Quantity must be an integer -- Quantity must be greater than 0 - -#### Review -- Rating must be present -- Rating must be an integer -- Rating must be between 1 and 5 - -## Process - -We expect you to follow the agile process we've learned about in class as you work on this project. - -### Team Leaders -Each team will have team leaders who are responsible for keeping track of each team member's contributions. Rotate leader roles at the beginning of the second week; every team member should be in at least one leader role during the project. - -- **Stand Up Leader** - - Notifies team members about meeting schedule and ensures that everyone is present and ready - - Takes notes about each person's daily report in Stand Up - - Keeps the meeting moving -- **Task Leader** - - Leads discussion on task assignment and prioritization - - Decide if a task should be completed alone or in a pair - - Assign tasks based on... - - Individual comfort - - Desire - - Ability - - Ensures the Kanban board stays up to date - -### Stand Up Meetings -The Stand Up Leader should determine the daily time for your stand up meeting with the team. Once you come up with a time, confirm with your PM that this time will work for them. - -At the end of each day, your team's assigned Project Manager will review the Trello board to ensure it captures the updates that your team has made throughout the day. - -### Interim Demo -In a real world work environment, a team's success is measured by their product as opposed to each individual's contribution. - -Each team will present their progress and respond to questions from their Project Manager on the first Friday. Every team member will participate in these demos; the PM will ask specific questions regarding -1. The team's progress and plan for completing the project -1. The technical decisions and implementation -1. Every team member's understanding of the underlying technical structures - -### Midpoint Retrospective - -On the first Friday, instead of our normal full-class retro, each team will get together and conduct a small-scale retro. One team member should facilitate, this may be the standup leader or someone else. Your retro should focus on the following questions: -- What has gone well? -- What could have gone better? -- Where is our direction still unclear? - -## Final Presentation -Each team will present their product in a final presentation to the group on the final Friday. Your presentation should be no more than 7 minutes. The presentation should include every team member and: -- what you learned as individuals and as a group -- a short story-driven demo of interesting features - -## Submission Guidelines -Your final project must be deployed to [Heroku](http://heroku.com). Your team will open a single pull request for the entire project. There are comprehension questions to answer with your submission that you should complete together as a group. Remember, you can submit a PR and still make some final changes to your code, so don't wait until the last minute. - -## What Instructors Are Looking For -Check out the [feedback template](feedback.md) which lists the items instructors will be looking for as they evaluate your project. +* ... 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/javascripts/application.js b/app/assets/javascripts/application.js new file mode 100644 index 0000000000..586eee9fa0 --- /dev/null +++ b/app/assets/javascripts/application.js @@ -0,0 +1,36 @@ +// 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 . +// Sticky Header +$(window).scroll(function() { + var scroll = $(window).scrollTop(); + + if (scroll >= 100) { + $(".top-nav").addClass("light-header"); + } else { + $(".top-nav").removeClass("light-header"); + } +}); + +// Year for copy content +$(function(){ +var theYear = new Date().getFullYear(); +$('#year').html(theYear); +}); 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/orders.js b/app/assets/javascripts/orders.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/orders.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/app/assets/javascripts/orders_items.js b/app/assets/javascripts/orders_items.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/orders_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/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/sessions.js b/app/assets/javascripts/sessions.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/sessions.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/app/assets/javascripts/users.js b/app/assets/javascripts/users.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/users.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..3b7d51a4d7 --- /dev/null +++ b/app/assets/stylesheets/application.scss @@ -0,0 +1,264 @@ +/* + * 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 scss content */ +@import "bootstrap"; +@import url("https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"); +@import url('https://fonts.googleapis.com/css?family=Butcherman|Creepster'); + +$primary-font: 'Gotham', gotham, Verdana,sans-serif; +$primary-teal: black; +// $primary-teal: #006699; +$dark-teal: #00796B; +$secondary-color: grey; +$light-gray: #EEEEEE; +$dark-gray: #424242; +$gold: #FF5722; + +$default-border: 2px solid; + +body { + // background: black; +} + +.carousel, .item, .active { + height:100%; +} +.carousel-inner { + width:100%; +} +.fill { + width:100%; + height:100%; + background-position:center; + background-size:cover; +} + +body, h1, h2, h3, h4, h5 { + font-family: $primary-font; +} + +h1, h2, h3, h4, h5 { + font-weight: bold; + font-family: $primary-font, cursive; +} + +a, h2, h3 { + color: black; +} + +a:hover { + color: $dark-teal; + text-decoration: none; +} + +/*------------------------*/ +/********************************** +Responsive navbar-brand image CSS +- Remove navbar-brand padding for firefox bug workaround +- add 100% height and width auto ... similar to how bootstrap img-responsive class works +***********************************/ + + +.navbar-brand { + padding: 0px; + font-family: "Butcherman"; +} +.navbar-brand>img { + height: 100%; + padding: 15px; + width: auto; +} + +section { + padding-top: 1em; +} + +h1.logo{ + font-family: 'Butcherman'; +} + +body, h1, h2, h3, h4, h5 { + font-family: $primary-font; +} + +h1, h2, h3, h4, h5 { + font-weight: bold; +} + +a, h2, h3 { + color: $primary-teal; +} + +a:hover { + color: $dark-teal; + text-decoration: none; +} + +.product_photo { + width: 100%; +} + + +body { + margin: 0; + padding: 0; + font-size: 100%; + line-height: 1.6; + font-family: Helvetica, sans-serif; +} + +#wrapper { + position: relative; + width: 100%; + min-height: 55vw; + overflow: hidden; +} + +.layer { + position: absolute; + width: 100vw; + min-height: 55vw; + overflow: hidden; +} + +.layer .content-wrap { + position: absolute; + width: 100vw; + min-height: 55vw; +} + +.layer .content-body { + position: absolute; + width: 25%; + top: 50%; + text-align: center; + transform: translateY(-50%); + color: #fff; +} + +.layer img { + position: absolute; + width: 35%; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +.layer h1 { + font-size: 2em; +} + +.bottom { + background: #222; + z-index: 1; +} + +.bottom .content-body { + right: 5%; +} + +.bottom h1 { + color: #fdab00; +} + +/* top layer */ +.top { + background: #eee; + color: #222; + z-index: 2; + width: 50vw; +} + +.top .content-body { + left: 5%; + color: #222; +} + +.handle { + position: absolute; + height: 100%; + display: block; + background-color: #fdab00; + width: 5px; /*for the bar*/ + top: 0; + left: 50%; + z-index: 3; +} + +/* skew effect */ +.skewed .handle { + top: 50%; + transform: rotate(30deg) translateY(-50%); + height: 200%; + transform-origin: top; +} + +.skewed .top { + transform: skew(-30deg); + margin-left: -1000px; + width: calc(50vw + 1000px); +} + +.skewed .top .content-wrap { + transform: skew(30deg); + margin-left: 1000px; +} + +@media(max-width: 768px) { + body { + font-size: 75%; + } +} + +.dashboard_container { + margin: 2rem; + padding: 2rem; +} + +table { + text-align: center; +} + +#merchant_options .btn { + padding: 5px; +} + +#dashboard_heading { + // color: #26A59A; + text-align: center; + padding: 2rem; + } + + #dashboard_container h2 { + // text-align: center; + padding: 1.5rem; + } + + #dashboard_container #order_filter li { + margin: 0.5rem; + } + + ul { + list-style-type: none; + // margin: 0; + padding: 0; + } + + + .content-half { + background-color: #b35919; + } diff --git a/app/assets/stylesheets/categories.scss b/app/assets/stylesheets/categories.scss new file mode 100644 index 0000000000..76702ab105 --- /dev/null +++ b/app/assets/stylesheets/categories.scss @@ -0,0 +1,5 @@ +.card-img-top { + width: 100%; + height: 15vw; + object-fit: cover; +} diff --git a/app/assets/stylesheets/orders.scss b/app/assets/stylesheets/orders.scss new file mode 100644 index 0000000000..741506954d --- /dev/null +++ b/app/assets/stylesheets/orders.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the Orders controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/orders_items.scss b/app/assets/stylesheets/orders_items.scss new file mode 100644 index 0000000000..631eb4a060 --- /dev/null +++ b/app/assets/stylesheets/orders_items.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the Orders_Items controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/products.scss b/app/assets/stylesheets/products.scss new file mode 100644 index 0000000000..bff386e55a --- /dev/null +++ b/app/assets/stylesheets/products.scss @@ -0,0 +1,3 @@ +// 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/ diff --git a/app/assets/stylesheets/sessions.scss b/app/assets/stylesheets/sessions.scss new file mode 100644 index 0000000000..ccb1ed25b2 --- /dev/null +++ b/app/assets/stylesheets/sessions.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the Sessions controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/users.scss b/app/assets/stylesheets/users.scss new file mode 100644 index 0000000000..31a2eacb84 --- /dev/null +++ b/app/assets/stylesheets/users.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the Users 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..2856c84143 --- /dev/null +++ b/app/controllers/application_controller.rb @@ -0,0 +1,63 @@ +class ApplicationController < ActionController::Base + before_action :current_order + before_action :find_merchant + before_action :find_user + + private + + def find_user + @current_user ||= User.find(session[:user_id]) if session[:user_id] + end + + def require_login + # Check if current user is logged in (has uid/provider) # Would have user_id and provider and uid + if !find_merchant + flash[:danger] = "You must be logged in to view this section" + redirect_to root_path + end + end + + def find_merchant + @merchant = User.find_by(id: session[:user_id], provider: 'github') + # What if it's nil? + end + + def find_user + @current_user ||= User.find(session[:user_id]) if session[:user_id] + end + + def current_order + # If nil, make one # Else, find and use the current one #Try and find the order + #To avoid getting a complete shopping cart add the complete + #at current order status will complete and it will create a new one + @current_order = Order.find_by(id: session[:order_id], status: "pending") + #if the @current_order object is nil it will return true + if @current_order.nil? # + #create @current_order if @current_order is nil + @current_order = Order.create(status: "pending") + session[:order_id] = @current_order.id + # else #needs to move above the create nil + # #write a test to show the weakness of a project + # return @current_order + # + #what happens if find_by returns nil because it can? + end + return @current_order + end + +# def current_order +# @order = Order.find_by(id:session[:order_id]) +# return @order if @order +# if the order has a merchant (use helper method?) +# @order = Order.find_by(user_id: @merchant.id, status: "pending")#to find pending orders for a merchant +# session[:order_id] = @order.id +# else #They are checking out as a guest, there won't be a user_id relation +# @order = Order.create(status: "pending") +# session[:order_id] = @order.id +# end +# #call set order are a merchant, find pending = nil. never going to next else to create one +# return @order +# #hint this method should have at least one case where we are seeing if session id is already set - +# #this will avoid assigning a new cart to somebody who has one +# end +end diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb new file mode 100644 index 0000000000..7a1eb3b735 --- /dev/null +++ b/app/controllers/categories_controller.rb @@ -0,0 +1,40 @@ +class CategoriesController < ApplicationController + before_action :require_login, only: [:new, :create] + + def index + @categories = Category.all.order(:name) + end + + def new + @category = Category.new + end + + def create + @category = Category.new(category_params) + + if @category.save + flash[:success] = "#{@category.name} added!" + redirect_to dashboard_path(@current_user.id) + else #save failed + flash.now[:danger] = "Category #{@category.name} not added!" + render :new, status: :bad_request + end + end + + def show + @category = Category.find_by(id: params[:id]) + + if @category.nil? + flash.now[:danger] = "Cannot find the category #{params[:id]}" + render :notfound, status: :not_found + 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/orders_controller.rb b/app/controllers/orders_controller.rb new file mode 100644 index 0000000000..bfb5b3d963 --- /dev/null +++ b/app/controllers/orders_controller.rb @@ -0,0 +1,96 @@ +class OrdersController < ApplicationController + before_action :find_order, only: [:edit,:update, :show] + + # + # def new #WRONG TO HAVE @ORDER + # #doesn't exist on the website + # #new is created by before action + # @order = Order.new + # # Find user + # # If no user, User.create + # if params[:user_id] + # @user_id = params[:user_id].to_i + # user = User.find_by(id: @user_id) + # if user.nil? + # flash.now[:warning] = "That user doesn't exit" + # end + # @order.user_id = @user_id + # end + # end + # def new + # @order = Order.new + # # Find user + # # If no user, User.create + # if params[:user_id] + # @user_id = params[:user_id].to_i + # user = User.find_by(id: @user_id) + # if user.nil? + # flash.now[:warning] = "That user doesn't exit" + # end + # @order.user_id = @user_id + # end + # end + + + + + def update + if @current_order && @current_order.update(order_params) + order = Order.find_by(id: @current_order.id, status: "pending") + order.status = "paid" + if order.orders_items.nil? + render :edit, status: :bad_request + else + order.orders_items.each do |o_i| + o_i.product.reduce_stock(o_i.quantity) + o_i.product.save + # if !o_i.product.save + # redirect_to order_path(@current_order.id) + # end + end + order.save + end + flash[:success] = "Order has been placed! Your ORDER CONFIRMATION ##{@current_order.id} " + redirect_to confirmation_path(@current_order.id) #Redirect to order confirmation + end + end + + def show + + end + + def edit + end + + def index + @orders = Order.all + end + + def confirmation + @confirmed_order = Order.find_by(id: params[:order_id]) + @items_in_cart = @confirmed_order.orders_items + @revenue_total = @items_in_cart.inject(0) { |sum, item| sum + item.calculate_total } + @date_placed = @confirmed_order.updated_at + @order_status = @confirmed_order.status + + end + + private + + def adjust_stock(order) + end + + def order_params + return params.require(:order).permit(:status, :street, :city, :state, :zip, :creditcard, :cvv, :billingzip, :ccexpiration, :name, :email, :user_id) + end + + def find_order + @order = Order.find_by(id: params[:id].to_i) + + if @order.nil? + flash.now[:danger] = "Cannot find the order #{params[:id]}" + render :notfound, status: :not_found + end + end + +end diff --git a/app/controllers/orders_items_controller.rb b/app/controllers/orders_items_controller.rb new file mode 100644 index 0000000000..4579ec7176 --- /dev/null +++ b/app/controllers/orders_items_controller.rb @@ -0,0 +1,49 @@ +class OrdersItemsController < ApplicationController +before_action :current_order +before_action :set_order_item +#add hidden field to products index each do captures price and quantity + +def update + # find order item by params id + + if order_item_params[:quantity].to_i == 0 + @orders_item.destroy + flash.now[:danger] = "Removed item from your cart" + redirect_to order_path(@current_order.id) + else + if @orders_item && @orders_item.product.can_purchase?(order_item_params[:quantity].to_i) && @orders_item.update(order_item_params) + flash[:success] = "The cart item #{@orders_item.product.name} has been updated" + redirect_to order_path(@current_order.id) + elsif @orders_item && !@orders_item.product.can_purchase?(order_item_params[:quantity].to_i) #the order item exists and it was invalid input for quantity + flash.now[:danger] = "There is not enought stock to add that quantity to the cart!" + redirect_to order_path(@current_order.id) + else + flash[:danger] = "Something went wrong" + redirect_to order_path(@current_order.id) + end + end +end + + def destroy + @orders_item = @current_order.orders_items.find(params[:id]) + @orders_item.destroy + @current_order.save + redirect_to order_path(@current_order) + end + + + private + + def set_order_item + @orders_item = OrdersItem.find(params[:id]) + if @orders_item.nil? + flash.now[:danger] = "Cannot find the item #{params[:id]} in the cart" + render :notfound + end + end + + def order_item_params + params.permit( :quantity) + end + +end diff --git a/app/controllers/products_controller.rb b/app/controllers/products_controller.rb new file mode 100644 index 0000000000..e7fbb9f7d7 --- /dev/null +++ b/app/controllers/products_controller.rb @@ -0,0 +1,104 @@ +class ProductsController < ApplicationController + before_action :find_product, only: [:show, :edit, :update, :retire] + before_action :require_login, except: [:index, :show, :add_to_cart, :root] + + # before_action :login, except: [:edit, :new] do we want to add this? + + def index + #pretty sure want to add an if statement to show by category's id but not sure if its connect in the schema correctly + @products = Product.all.order(:name) + end + #@task.update( + #name: params[:task][:name], + #description: params[:task][:description], + #completion_date: params[:task][:completion_date]) + + def show; end + + def add_to_cart + #calling method add_product in model that + quantity = 1 #if you call this method you get at least one item + #1 becomes the default so you have at least one order item + if !params[:quantity].nil? #making sure not nil + #quantity comes in as a string, to avoid spam comes as 0 + #quantity - checks for positive number greater than one. if you return put + #-4 you would get -4 but with the .max value it get the biggest number + #if they don't pass in a number the default is 1 + quantity = [params[:quantity].to_i, quantity].max + end + + product = Product.find_by(id: params[:product_id].to_i) + + if product.can_purchase?(params[:quantity].to_i) + + @current_order.add_product(params[:product_id], quantity) + redirect_to products_path + else + flash[:danger] = "There is not enought stock to add that quantity to the cart!" + redirect_to product_path(params[:product_id]) + end + end + + def new + @product = Product.new + @categories = Category.all + # There should be a merchant to create a new product + # @merchant should be available via application controller + if @merchant + @product.user_id = @merchant.id + else + flash.now[:warning] = "That merchant does not exist" + end + end + + def edit; end + + def create + @product = Product.new(product_params) + @product.categories + @product.user_id = @current_user.id + + if @product.save + flash[:success] = 'Product Created!' + + redirect_to dashboard_path(@current_user.id) + + else + flash.now[:danger] = 'Product not created!' + + render :new, status: :bad_request + end + end + + def update + # params[:product][:category_ids] ||= [] #if category_ids returns nil it will be set to empty array + if @product && @product.update(product_params) + redirect_to product_path(@product.id) + elsif @product && !@product.valid? #the product exists and it was invalid inputs + render :edit, status: :bad_request + end + end + + def destroy + end + + + + private + + def find_product + @product = Product.find_by(id: params[:id].to_i) + # render_404 unless @product + + if @product.nil? + flash.now[:danger] = "Cannot find the product #{params[:id]}" + render :notfound, status: :not_found + end + end + + def product_params + #can i access :category_id? not unless there isa belongs_to + return params.require(:product).permit(:user_id, :name, :price, :description, :photo, :stock, category_ids:[]) + end + +end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb new file mode 100644 index 0000000000..79b383ca47 --- /dev/null +++ b/app/controllers/sessions_controller.rb @@ -0,0 +1,46 @@ +class SessionsController < ApplicationController + # TODO How do we handle a merchant shopping/closing browser + # coming back as a user (not logged in). + # Do we merge the carts or do we destroy the old cart + + def login + + auth_hash = request.env['omniauth.auth'] + + user = User.find_by(uid: auth_hash[:uid], provider: 'github') + + # + + if user + # User was found in the database + flash[:success] = "Logged in as returning user #{user.name}" + + else + #TODO Reroute to signup - Signup does this stuff + # User doesn't match anything in the DB + # Attempt to create a new user + user = User.build_from_github(auth_hash) + # + if user.save + flash[:success] = "Logged in as new user #{user.name}" + + else + # Couldn't save the user + # + flash[:error] = "Could not create new user account: #{user.errors.messages}" + redirect_to root_path + return + end + end + # If we get here, we have a valid user instance + session[:user_id] = user.id + redirect_to root_path + end + + def destroy + session[:user_id] = nil + flash[:success] = "Successfully logged out!" + redirect_to root_path + end + +end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb new file mode 100644 index 0000000000..0f0d3b5527 --- /dev/null +++ b/app/controllers/users_controller.rb @@ -0,0 +1,146 @@ +class UsersController < ApplicationController + # May need to add edit/update/destroy to this later + before_action :find_user, only: [:dashboard] + before_action :require_login, only: [:dashboard] + +# Might not need this as login does new oauth + # def new + # @user = User.new + # end + + def index + # this works in rails c but not on website...help? + @merchants = User.all.select { |user| user.uid } + # Even users says its nil, maybe a seed issue? + @users = User.all + + end + + + def products + # Show products for the merchant = working + @merchant = User.find_by(id: params[:user_id]) + end + + def create + @user = User.new(user_params) + if @user.save + # We run User.create on the checkout page + # (Merchant creation is done via oauth login) + flash[:success] = "Account saved!" + redirect_to root_path + else + # Could not save + flash[:danger] = "Could not save account" + redirect_to root_path + end + end + + #TODO Do we need update? Are we letting merchants edit/update? + +# Is a page that shows all merchant's products +# # If person is merchant of that id, can edit products +# Instead of show i'm setting up a product page per merchant + + + def products + # Find merchant id + # Find products linked to that merchant + # Show them + id = params[:user_id].to_i + @merchant = User.find_by(id: id) + + if @merchant.nil? + render :notfound, status: :not_found + end + @products = @merchant.products.all + + end + + # Requires login to see merchant dashboard + # Will show products, able to add product, edit product, see orders, etc. + def dashboard + # @user = find_user + #Check if current user is the person for the dashboard view page + # Is this how that works? Can't test until Github is back up. + # raise + if session[:user_id] != params[:id].to_i + flash[:warning] = "You can only view your own dashboard" + redirect_to root_path, status: :bad_request + else + + # @merchant = find_merchant - have this already + ######## SORT THESE THINGS !!!! + @products = @merchant.products.sort_by{ |item| item.name } + if params[:status] && params[:status] != "all" + @order_items = @merchant.order_items_for_status(params[:status]).sort_by{ |item| item.order_id } + elsif + @order_items = @merchant.sold_items.sort_by{ |item| item.order_id } + end + + + order_overview = {} + @order_items.each do |item| + # Check if item order number is already in array + if order_overview[item.order_id] + order_overview[item.order_id] += 1 + else + order_overview[item.order_id] = 1 + end + # if order_numbers.!include?(item.order_id) + # # If not, add it + # order_numbers << item + # end + @total_orders = order_overview.length + @total_items = order_overview.values.sum + end + @revenue_total = @order_items.inject(0) { |sum, item| sum + item.calculate_total } + # For each product, search for order items + # Params data - assign to filter - value available in the view - ? + # Have filter method, + # Assign filter from params if it exists or user a default or all + # In partial, use just the filter items ? + # Links, get some quesries, use them as needed in controller + # QUERY STRING and Link to + @status_types = ["pending", "paid", "complete", "cancelled"] + + # TODO - Show summary of orders / revenue + # @order_itmes = [] + # @order_items = @merchant.products.map do |product| + # + # Find order items with product id + # OrdersItem.select { |item| item.product_id == product.id } + # Returns array of products, one per order entry + # Would also need the order for each product. + # Not sure how this works + # @order_items << product.orders_items + # product.orders_items + # end + end + # + # # How do we find order items for merchant? merchant.orderitems? + # orderitems.where(merchant_id == @merchant.id) ? + # @order_items_for_merchant = @merchant.orders_items + + end + + private + +# Do I need this or can I just use the find_merchant method? + def find_user + # Try to find the user + @user = User.find_by(id: params[:id].to_i) + # + # If user doesn't exist flash a message /render notfound page + if @user.nil? + flash.now[:danger] = "Cannot find user" + # + render :notfound , status: :not_found + end + end + + def user_params + + return params.require(:user).permit(:name, :email, :photo, :uid, :provider) + end +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb new file mode 100644 index 0000000000..752c597058 --- /dev/null +++ b/app/helpers/application_helper.rb @@ -0,0 +1,9 @@ +module ApplicationHelper + def readable_price(price) + ("" + number_with_precision(price, :precision => 2, :delimiter => ',') + "").html_safe + end + + def format_date(date) + ("" + date.strftime('%b %e, %Y')+ "").html_safe + end +end diff --git a/app/helpers/categories_helper.rb b/app/helpers/categories_helper.rb new file mode 100644 index 0000000000..e06f31554c --- /dev/null +++ b/app/helpers/categories_helper.rb @@ -0,0 +1,2 @@ +module CategoriesHelper +end diff --git a/app/helpers/orders_helper.rb b/app/helpers/orders_helper.rb new file mode 100644 index 0000000000..7f94d1f77e --- /dev/null +++ b/app/helpers/orders_helper.rb @@ -0,0 +1,55 @@ +module OrdersHelper + def us_states + states =["AL", + "AK", + "AZ", + "AR", + "CA", + "CO", + "CT", + "DE", + "FL", + "GA", + "HI", + "ID", + "IL", + "IN", + "IA", + "KS", + "KY", + "LA", + "ME", + "MD", + "MA", + "MI", + "MN", + "MS", + "MO", + "MT", + "NE", + "NV", + "NH", + "NJ", + "NM", + "NY", + "NC", + "ND", + "OH", + "OK", + "OR", + "PA", + "RI", + "SC", + "SD", + "TN", + "TX", + "UT", + "VT", + "VA", + "WA", + "WV", + "WI", + "WY"] + return states + end +end diff --git a/app/helpers/orders_items_helper.rb b/app/helpers/orders_items_helper.rb new file mode 100644 index 0000000000..e4770ebcd9 --- /dev/null +++ b/app/helpers/orders_items_helper.rb @@ -0,0 +1,2 @@ +module OrdersItemsHelper +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/sessions_helper.rb b/app/helpers/sessions_helper.rb new file mode 100644 index 0000000000..309f8b2eb3 --- /dev/null +++ b/app/helpers/sessions_helper.rb @@ -0,0 +1,2 @@ +module SessionsHelper +end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb new file mode 100644 index 0000000000..2310a240d7 --- /dev/null +++ b/app/helpers/users_helper.rb @@ -0,0 +1,2 @@ +module UsersHelper +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..0e0d40e488 --- /dev/null +++ b/app/models/category.rb @@ -0,0 +1,24 @@ +class Category < ApplicationRecord + has_and_belongs_to_many :products + + validates :name, uniqueness: true, presence: true + + # def self.product_photo + # photos = [] + # Product.all.each do |product| + # photos << product.photo + # end + # return photos.sample + # end + + def product_photo + photos = [] + self.products.each do |product| + photos << product.photo + end + return photos.sample + end + + + +end diff --git a/app/models/concerns/.keep b/app/models/concerns/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/models/order.rb b/app/models/order.rb new file mode 100644 index 0000000000..07e282dae0 --- /dev/null +++ b/app/models/order.rb @@ -0,0 +1,66 @@ + class Order < ApplicationRecord + has_many :orders_items, dependent: :destroy + #if you destroy the order items associated with it + has_many :products, through: :orders_items, dependent: :destroy + validates_inclusion_of :status, :in => ["pending", "paid","complete", "cancelled"], presence: :true + + belongs_to :user, optional: true + + #validation so that a user cannot have any products that have their user_id + + # validates :status, presence: true, inclusion: { in: %w(pending complete paid cancelled)} + # validates :street, presence: true + # validates :city, presence: true + # validates :state, presence: true, inclusion: { in: %w(AL AK AZ AR CA CO CT DE FL GA HI ID IL IN IA KS KY LA ME MD MA MI MN MS MO MT NE NV NH NJ NM NY NC ND OH OK OR PA RI SC SD TN TX UT VT VA WA WV WI WY)} + # validates :zip, presence: true, length: {is: 5}, numericality: { only_integer: true } + # validates :creditcard, presence: true, length: {is: 4},numericality: { only_integer: true } + # validates :cvv, presence: true, length: {is: 3},numericality: { only_integer: true } + # validates :billingzip, presence: true, length: {is: 5},numericality: { only_integer: true } + + + # { + # orders_item: { + # product_id: 1, + # quantity: 20 + # } + # } + def add_product(product_id, quantity) + #Find out if the current product is in the cart + current_item = OrdersItem.find_by(product_id: product_id, order_id: self.id) + + #If current_product is in the cart then + if current_item + #the current_product is an instance of OrdersItem which has quantity attribute + #increase the quantity then update it + current_item.quantity += quantity + current_item.save + else + #create a new instance of OrderItems with product params + #this is what OrderItems was doing - if you have an orderitem you + #want to add it to the order + #when you update cart will create order item + current_item = OrdersItem.create(product_id: product_id, + quantity: quantity, + order_id: self.id) + end + + return current_item + end + + + def order_calculate_total + total_cost = 0 + self.orders_items.each do |item| + total_cost += item.calculate_total + end + return total_cost + end + + def items_in_cart + # TODO: Update to return accurate quanity of all items in cart + return self.orders_items.count + end + + + +end diff --git a/app/models/orders_item.rb b/app/models/orders_item.rb new file mode 100644 index 0000000000..a092a2ea94 --- /dev/null +++ b/app/models/orders_item.rb @@ -0,0 +1,10 @@ +class OrdersItem < ApplicationRecord + belongs_to :product + belongs_to :order + validates :quantity, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 0 } + + + def calculate_total + return self.product.price * self.quantity + end +end diff --git a/app/models/product.rb b/app/models/product.rb new file mode 100644 index 0000000000..d03dbf1679 --- /dev/null +++ b/app/models/product.rb @@ -0,0 +1,38 @@ +class Product < ApplicationRecord + has_and_belongs_to_many :categories # do we need dependent: :destroy here? + belongs_to :user + has_many :orders_items + has_many :orders, through: :orders_items + + # Merchant can only have unique products + validates :name, presence: true + + validates_uniqueness_of :name, :scope => [:user_id] + # message: "user already has a product with that name" } + validates :price, presence: true, numericality: {greater_than: 0} + + validates :description, presence: true + validates :photo, presence: true + validates :stock, presence: true, numericality: { greater_than_or_equal_to: 0 } + + + + def self.category_list + categories = [] + Product.all.each do |product| + product.categories.each do |category| + categories << category + end + end + return categories.uniq + end + + def reduce_stock(quantity) + self.stock = self.stock - quantity + end + + def can_purchase?(quantity) + stock >= quantity + end + +end diff --git a/app/models/user.rb b/app/models/user.rb new file mode 100644 index 0000000000..189bbe5620 --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,45 @@ + +class User < ApplicationRecord + + has_many :orders, dependent: :destroy + has_many :products, dependent: :destroy + validates :email, presence: true, format: { with: /.+@.+\..+\z/ }, uniqueness: true + validates :name, presence: true + + def self.build_from_github(auth_hash) + new_user = User.new + + new_user.uid = auth_hash[:uid] + new_user.provider = 'github' + new_user.name = auth_hash[:info][:name] + new_user.email = auth_hash[:info][:email] + + return new_user + end + + def sold_items + # sold items array + # loop through products of merchant + # loop through orders_items + # Push each item to array + # return the array + # in view, check if array is empty before trying to show things + @my_sold_items = [] + self.products.each do |product| + # Checking if product has order items + product.orders_items.each do |item| + # Pushing each item into my_sold_items + @my_sold_items << item + end + end + return @my_sold_items + end + + # Helper method to return order items for different status types + def order_items_for_status(status) + self.sold_items.select { |item| item.order.status == status.downcase } + end + + + +end diff --git a/app/views/categories/_form.html.erb b/app/views/categories/_form.html.erb new file mode 100644 index 0000000000..4db2cafa7d --- /dev/null +++ b/app/views/categories/_form.html.erb @@ -0,0 +1,29 @@ +
+ <% if @category.errors.any? %> + + <% end %> + + + +
+ diff --git a/app/views/categories/index.html.erb b/app/views/categories/index.html.erb new file mode 100644 index 0000000000..350b719fe2 --- /dev/null +++ b/app/views/categories/index.html.erb @@ -0,0 +1,25 @@ +
+ +
+

Categories

+ +
+ + <%# @category_photos.each do |photo| %> + <% @categories.each do |category| %> +
+
+ <% if !category.product_photo.nil? %> + <%= link_to image_tag(category.product_photo, alt:"Card image cap", class:"card-img-top img-fluid"), category_path(category.id) %> +
+

<%= category.name %>

+
+
+
+ <% end %> + <% end %> + + + + +
diff --git a/app/views/categories/new.html.erb b/app/views/categories/new.html.erb new file mode 100644 index 0000000000..2f055f4698 --- /dev/null +++ b/app/views/categories/new.html.erb @@ -0,0 +1,3 @@ +
+<%= render partial: "form", locals: { action_name: "Create New Category", button_title: 'Save' } %> +
diff --git a/app/views/categories/notfound.html.erb b/app/views/categories/notfound.html.erb new file mode 100644 index 0000000000..c37ab2ebaa --- /dev/null +++ b/app/views/categories/notfound.html.erb @@ -0,0 +1 @@ +

Not Found 404

diff --git a/app/views/categories/show.html.erb b/app/views/categories/show.html.erb new file mode 100644 index 0000000000..7e0ab0e066 --- /dev/null +++ b/app/views/categories/show.html.erb @@ -0,0 +1,14 @@ +
+

Category Details Page

+
+ + +
+ <%# raise %> +

<%= @category.name %>

+ <% @category.products.each do |product| %> + + <% end %> +
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb new file mode 100644 index 0000000000..a2ca7ca5c0 --- /dev/null +++ b/app/views/layouts/application.html.erb @@ -0,0 +1,78 @@ + + + + Thrillow + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + + + <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> + <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> + + +
+ +
+ <% flash.each do |name, message| %> +
<%=message %>
+ <% end %> +
+
+ <% flash.each do |name, message| %> + <% if message.class == Array %> +
+ <% message.each do |msg| %> +

<%= "#{name}:" %><%= " #{msg}" %>

+ <% end %> +
+ <% else %> +
+ <%= message %> +
+ <% end %> + <% end %> +
+
+
+ <%= yield %> +
+ + diff --git a/app/views/layouts/mailer.html.erb b/app/views/layouts/mailer.html.erb new file mode 100644 index 0000000000..cbd34d2e9d --- /dev/null +++ b/app/views/layouts/mailer.html.erb @@ -0,0 +1,13 @@ + + + + + + + + + <%= yield %> + + diff --git a/app/views/layouts/mailer.text.erb b/app/views/layouts/mailer.text.erb new file mode 100644 index 0000000000..37f0bddbd7 --- /dev/null +++ b/app/views/layouts/mailer.text.erb @@ -0,0 +1 @@ +<%= yield %> diff --git a/app/views/orders/_form.html.erb b/app/views/orders/_form.html.erb new file mode 100644 index 0000000000..8ec107cade --- /dev/null +++ b/app/views/orders/_form.html.erb @@ -0,0 +1,183 @@ + + + + + +
+
+ +

Checkout

+
+ +
+
+

+ Your cart + 3 +

+
    + <% @current_order.orders_items.each do |item| %> +
  • +
    +
    <%=item.product.name%>
    + Qty : <%=item.quantity%> +
    + $<%=readable_price(item.product.price)%> +
  • + <% end %> +
  • + Total (USD) + <%="#{number_to_currency(@current_order.order_calculate_total)}" %> +
  • +
+
+ + +
+

Billing address

+ <%= form_with model: @current_order, class: "needs-validation" do |f|%> +
+ <%= f.label :name %> +
+ <%= f.text_field :name, class: "form-control" ,id:"username", required:""%> +
+ Your name is required. +
+
+
+
+ + +
+ Please enter a valid email address for shipping updates. +
+
+ +
+ + +
+ Please enter your shipping address. +
+
+ +
+ + +
+ +
+
+ + +
+ Please select a valid country. +
+
+
+ <%= f.label :state %> + <%= select_tag :state, options_for_select(us_states), class:"custom-select d-block w-100"%> +
+ Please provide a valid state. +
+
+
+ <%= f.label :zip %> + <%= f.text_field :zip, class: "form-control", id: "zip" %> +
+ Zip code required. +
+
+
+
+
+ + +
+
+ + +
+
+ +

Payment

+ +
+
+ + + Full name as displayed on card +
+ Name on card is required +
+
+
+ <%= f.label :creditcard %> + +
+ Credit card number is required +
+
+
+
+
+ <%= f.label :ccexpiration %> + <%= f.text_field :ccexpiration, class: "form-control", id: "cc-expiration" %> +
+ Expiration date required +
+
+
+ <%= f.label :cvv %> + <%= f.text_field :cvv, class: "form-control", id: "cc-cvv" %> +
+ Security code required +
+
+
+
+ <%= f.submit 'Submit Checkout', class: "btn btn-primary btn-lg btn-block"%> + <%= flash[:message] %> + +
+
+ <% end %> + + + + + + + + + + + + + + diff --git a/app/views/orders/confirmation.html.erb b/app/views/orders/confirmation.html.erb new file mode 100644 index 0000000000..a34b8373ec --- /dev/null +++ b/app/views/orders/confirmation.html.erb @@ -0,0 +1,69 @@ +
+ + +

Confirmation

+ + + + + + + + + + <% @items_in_cart.each do |item| %> + + + + + + <% end %> + +
+ Item + + Subtotal + + Description +
+ <%= "#{item.product.name}" %> + + <%= number_to_currency(item.calculate_total) %> + + <%= link_to "#{item.product.name.capitalize}", product_path(item.product.id) %> +
+ + + + + + + + + + + + + + + + + +
+ Total + + Time Placed + + Status +
+ <%= number_to_currency(@revenue_total) %> + + <%= format_date(@date_placed) %> + + <%= @order_status.capitalize %> +
+ +
diff --git a/app/views/orders/edit.html.erb b/app/views/orders/edit.html.erb new file mode 100644 index 0000000000..a3843446a3 --- /dev/null +++ b/app/views/orders/edit.html.erb @@ -0,0 +1,3 @@ +<%= render partial: "form", + locals: { action_name: 'Complete Order', + button_title: 'Complete Order' } %> diff --git a/app/views/orders/notfound.html.erb b/app/views/orders/notfound.html.erb new file mode 100644 index 0000000000..f0d31a54d2 --- /dev/null +++ b/app/views/orders/notfound.html.erb @@ -0,0 +1 @@ +

Order not found

diff --git a/app/views/orders/show.html.erb b/app/views/orders/show.html.erb new file mode 100644 index 0000000000..a8c764a70e --- /dev/null +++ b/app/views/orders/show.html.erb @@ -0,0 +1,66 @@ + + + + + + +<% if @current_order.products.count == 0 %> +
+

Shopping Cart

+

Your cart is empty

+

Pick your next nightmare home at Shop All Homes above!

+
+ +<% else %> + +
+ + + + + + + + + + + + <% @current_order.orders_items.each do |item| %> + + + + + + + + <%end%> + + + + + + + + + + + + +
ProductPriceQuantitySubtotal
+
+ +
+

<%= item.product.name %>

+

<%= item.product.description %>

+
+
+
$<%= readable_price(item.product.price) %> + <%= form_with url:order_orders_item_path(@current_order.id,item.id), method: :patch do |f|%> + <%= f.text_field :quantity, value: "#{item.quantity}", class: "form-control text-center" %> + <%= f.submit "Update Cart"%> + <% end %> + $<%= readable_price(item.calculate_total)%> + <%= link_to "Remove", order_orders_item_path(@current_order.id, item.id) , class: "btn btn-danger btn-sm", method: :delete%> +
<%=readable_price(@current_order.order_calculate_total)%>
<%= link_to "Continue Shopping", products_path, class: "btn btn-dark"%><%= link_to "Complete Order", edit_order_path(@current_order.id), class: "btn btn-success btn-block"%>
+
+<%end%> diff --git a/app/views/products/_form.html.erb b/app/views/products/_form.html.erb new file mode 100644 index 0000000000..00304f0c4d --- /dev/null +++ b/app/views/products/_form.html.erb @@ -0,0 +1,41 @@ +
+
+ <% if @product.errors.any? %> +
    + <% @product.errors.each do |column, message| %> +
  • + <%= column.capitalize %> <%= message %> +
  • + <% end %> +
+ <% end %> + + <%= form_with model: @product, class: "form" do |f|%> +
    + + <%# f.collection_check_boxes :category_ids, Category.all, :id, :name%> +
  • <%= f.label :categories %>
  • +
  • <%= collection_check_boxes(:product, :category_ids, Category.all, :id, :name) %>
  • + +
  • <%= f.label :price %>
  • +
  • <%= f.text_field :price, class: ""%>
  • + +
  • <%= f.label :name %>
  • +
  • <%= f.text_field :name, class: ""%>
  • + + +
  • <%= f.label :description %>
  • +
  • <%= f.text_field :description, class: ""%>
  • + +
  • <%= f.label :stock %>
  • +
  • <%= f.text_field :stock, class: "" %>
  • + +
  • <%= f.label :photo %>
  • +
  • <%= f.text_field :photo, class: "" %>
  • + + <%= f.submit button_title, class: 'btn btn-info'%> + <% end %> +
+ +
+
diff --git a/app/views/products/edit.html.erb b/app/views/products/edit.html.erb new file mode 100644 index 0000000000..763606f871 --- /dev/null +++ b/app/views/products/edit.html.erb @@ -0,0 +1,8 @@ +
+
+
+ + +
+<%= render partial: "form", locals: { action_name: "Edit Product", button_title: 'Save' } %> +
diff --git a/app/views/products/index.html.erb b/app/views/products/index.html.erb new file mode 100644 index 0000000000..0ed273787f --- /dev/null +++ b/app/views/products/index.html.erb @@ -0,0 +1,25 @@ +
+
+
+ +
+<% @products.each do |product| %> +
+
+
+ <%= link_to image_tag(product.photo, class: "product_photo"),product_path(product.id) %> +
<%= link_to product.name , product_path(product.id), class: "btn-overlay"%>
+
+
+ <%= link_to product.user.name, merchant_products_path(product.user.id), class: "title"%> +
+
+ $<%=readable_price(product.price) %> +
+ <%= link_to "Add to Cart", product_add_to_cart_path(product.id), method: :post, class: "btn btn-primary btn-sm add-to-cart"%> +
+
+
+
+<%end%> +
diff --git a/app/views/products/new.html.erb b/app/views/products/new.html.erb new file mode 100644 index 0000000000..d679e6d6d9 --- /dev/null +++ b/app/views/products/new.html.erb @@ -0,0 +1,3 @@ +
+<%= render partial: "form", locals: { action_name: "Create New Product", button_title: 'Save' } %> +
diff --git a/app/views/products/notfound.html.erb b/app/views/products/notfound.html.erb new file mode 100644 index 0000000000..c37ab2ebaa --- /dev/null +++ b/app/views/products/notfound.html.erb @@ -0,0 +1 @@ +

Not Found 404

diff --git a/app/views/products/root.html.erb b/app/views/products/root.html.erb new file mode 100644 index 0000000000..3183957837 --- /dev/null +++ b/app/views/products/root.html.erb @@ -0,0 +1,117 @@ + + + + +
+
+
+

Who We Are

+
We are the leading real estate marketplace
+
dedicated to connecting all homebuyers
+
to their nightmare home.
+
+
+
+
+
+ +

Zombies

+

From cemetaries to remote fields find the perfect dwelling grounds

+
+
+
+
+ +

Werewolves

+

Find the perfect forest with unobstructed moon views

+
+
+
+
+ +

Spooky

+

Want a home with a different thrill? You will never know what to expect with these haunted homes

+
+
+
+
+
+
+ + + +
+
+
+
+
+

Your Unobstructed Moon View Awaits

+
+

Be ready to buy and be thrilled!

+
+
    +
  • + + + Trustwrothy + +
  • +
  • + + + Transformative + +
  • +
  • + + + Thrillow + +
  • +
+
+
+ +
+
+
diff --git a/app/views/products/show.html.erb b/app/views/products/show.html.erb new file mode 100644 index 0000000000..f30a854f8d --- /dev/null +++ b/app/views/products/show.html.erb @@ -0,0 +1,31 @@ +
+
+
+
+ <% if @product.nil? %> +

404 Not found

+ <% else %> +

<%=@product.name.capitalize%>

+
<%#@product.photo %>
+ + <% end %> + + <%= form_with url: product_add_to_cart_path(@product.id) do |f|%> + <%= f.label :quantity %> + <%= f.text_field :quantity %> + <%= f.submit "Add to Cart" %> + <% end %> + +
+ + + + + + <%# link_to "Edit", edit_product_path(@product.id) %> + <%# link_to "Delete #{@product.title}", product_path(@product.id), method: :delete %> +<%# end %> diff --git a/app/views/sessions/login.html.erb b/app/views/sessions/login.html.erb new file mode 100644 index 0000000000..b90f508e95 --- /dev/null +++ b/app/views/sessions/login.html.erb @@ -0,0 +1,3 @@ + +

Sessions#login

+

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

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

Please Log in

+ +<%= form_with model: @user, url: login_path, method: :post do |f| %> + <%= f.label :name, 'Username' %> + <%= f.text_field :name, placeholder: 'Username' %> + <%= f.submit 'Login' %> +<% end %> diff --git a/app/views/users/_statussummary.html.erb b/app/views/users/_statussummary.html.erb new file mode 100644 index 0000000000..1ebed4ddcc --- /dev/null +++ b/app/views/users/_statussummary.html.erb @@ -0,0 +1,94 @@ + +

<%= "Order Summary" %>

+ + + + + + + + + + + + + +
+ <%= "Revenue" %> + + <%= "Number of Orders" %> +
+ <%= number_to_currency(@revenue_total) %> + + <%= "#{@total_orders}" %> +
+ + + + + + + + + + + + + + + + + + + + <% @order_items.each do |item| %> + + + + + + + + + + + <% end %> +
+ <%= "Order" %> + + <%= "Date" %> + + <%= "Product" %> + + <%= "Price" %> + + <%= "Quantity" %> + + <%= "Subtotal" %> + + <%= "Order Status" %> +
+ <%= link_to "#{item.order.id}", order_path(item.order.id) %> + + <%= format_date(item.order.updated_at) %> + + <%= link_to "#{item.product.name.capitalize}", product_path(item.product.id) %> + + <%= number_to_currency(item.product.price) %> + + <%= item.quantity %> + + <%= number_to_currency(item.calculate_total) %> + + <%= item.order.status.capitalize %> +
diff --git a/app/views/users/dashboard.html.erb b/app/views/users/dashboard.html.erb new file mode 100644 index 0000000000..7d3b8f1cb4 --- /dev/null +++ b/app/views/users/dashboard.html.erb @@ -0,0 +1,91 @@ +
+
+
+ + +<% if @user.nil? %> +

User Not Found

+<% else %> + +
+
+

<%= "#{@merchant.name}'s Dashboard" %>

+ + +
+ + + + +

My Products

+ + + + + + + + + + + + <% @merchant.products.each do |product| %> + + + + + + + + + <% end %> + +
+ Name + + Price + + Description + + Stock + + Edit +
+ <%= link_to product.name.capitalize, product_path(product.id) %> + + <%="#{number_to_currency(product.price)}" %> + + <%= "#{product.description}" %> + + <%= "#{product.stock}" %> + + <%= link_to "Edit", edit_product_path(product.id) %> +
+ +
+

Orders

+ + <%= render partial: "statussummary" %> + +
+
+<% end %> diff --git a/app/views/users/index.html.erb b/app/views/users/index.html.erb new file mode 100644 index 0000000000..94fef37fa8 --- /dev/null +++ b/app/views/users/index.html.erb @@ -0,0 +1,25 @@ +
+
+
+
+

Agents

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

<%= link_to "#{merchant.name}", merchant_products_path(merchant.id) %>

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

User not found

diff --git a/app/views/users/products.html.erb b/app/views/users/products.html.erb new file mode 100644 index 0000000000..cb4fa14a54 --- /dev/null +++ b/app/views/users/products.html.erb @@ -0,0 +1,35 @@ +
+

Agents

+ + <% if @merchant.nil? %> +

Merchant Not Found

+ <% else %> +

<%= @merchant.name %>

+

+ <%= @merchant.email %> +

+ + <% end %> + +
+ <% @merchant.products.each do |product| %> +
+
+
+ <%= link_to image_tag(product.photo, class: "product_photo"),product_path(product.id) %> +
<%= link_to product.name , product_path(product.id), class: "btn-overlay"%>
+
+
+ <%= link_to product.user.name, merchant_products_path(product.user.id), class: "title"%> +
+
+ $<%=readable_price(product.price) %> +
+ <%= link_to "Add to Cart", product_add_to_cart_path(product.id), method: :post, class: "btn btn-primary btn-sm"%> +
+
+
+
+ <%end%> +
+
diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb new file mode 100644 index 0000000000..32467c3683 --- /dev/null +++ b/app/views/users/show.html.erb @@ -0,0 +1,28 @@ + +<% if @user.nil? %> +

User Not Found

+<% else %> + + +

<%= @user.name %>

+

+ <%= @user.email %> +

+ +<% end %> + +
+

Products

+ +
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..5c09a3eefc --- /dev/null +++ b/config/application.rb @@ -0,0 +1,25 @@ +require_relative 'boot' + +require 'rails/all' + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(*Rails.groups) + +module Betsy + class Application < Rails::Application + config.generators do |g| + # Force new test files to be generated in the minitest-spec style + g.test_framework :minitest, spec: true + # Always use .js files, never .coffee + g.javascript_engine :js + end + # Initialize configuration defaults for originally generated Rails version. + config.load_defaults 5.2 + + # Settings in config/environments/* take precedence over those specified here. + # Application configuration can go into files in config/initializers + # -- all .rb files in that directory are automatically loaded after loading + # the framework and any gems in your application. + end +end diff --git a/config/boot.rb b/config/boot.rb new file mode 100644 index 0000000000..b9e460cef3 --- /dev/null +++ b/config/boot.rb @@ -0,0 +1,4 @@ +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) + +require 'bundler/setup' # Set up gems listed in the Gemfile. +require 'bootsnap/setup' # Speed up boot time by caching expensive operations. diff --git a/config/cable.yml b/config/cable.yml new file mode 100644 index 0000000000..dd2a324c68 --- /dev/null +++ b/config/cable.yml @@ -0,0 +1,10 @@ +development: + adapter: async + +test: + adapter: async + +production: + adapter: redis + url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> + channel_prefix: betsy_production diff --git a/config/credentials.yml.enc b/config/credentials.yml.enc new file mode 100644 index 0000000000..f3ace6252e --- /dev/null +++ b/config/credentials.yml.enc @@ -0,0 +1 @@ +Yrstgg9sorjB/ZS1a5+M/U2GLzIl0iktu5DZoqKVSrfFOjklP4AZ6hl+uhygpNm8LVT4bidJ9LB8chGqCy4AWlBSUxO2HwZFoOsuPWgjRXDrUdvddZzu+Nlqwe+Tj65ncHxSkKNzjA8ovyRiQgTfj7R4uzeoHjGKCtbBbVSw9LqKcFh75B/hBYGd0Qku6cY7CB1Bfbdwo52vxMdp5SNTAvtGmxYYEpXBF/K8XQOG2S9Wmt+N+RaacZfpw8xTSxdGCc8qo8GmTbugjHn6gsTnkPBCOi2iaqe3WIWHs2pNyGNTOh+tjSxSaObGqS/jeYDLv1MCWBk2raIfMSAyEkBrDGCh9vpWAL+qZu1baEbTsVKlUvNLnVoI7Bziti233RhrRHsoHme3Cj6qOUA7/r77+/4MXSE5EtcUXQ+m--iYF4uUdqfhBX+QPj--4p+KRRR2mbsZrMhQvcjuXQ== \ 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/application_controller_renderer.rb b/config/initializers/application_controller_renderer.rb new file mode 100644 index 0000000000..89d2efab2b --- /dev/null +++ b/config/initializers/application_controller_renderer.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +# ActiveSupport::Reloader.to_prepare do +# ApplicationController.renderer.defaults.merge!( +# http_host: 'example.org', +# https: false +# ) +# end diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb new file mode 100644 index 0000000000..4b828e80cb --- /dev/null +++ b/config/initializers/assets.rb @@ -0,0 +1,14 @@ +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = '1.0' + +# Add additional assets to the asset load path. +# Rails.application.config.assets.paths << Emoji.images_path +# Add Yarn node_modules folder to the asset load path. +Rails.application.config.assets.paths << Rails.root.join('node_modules') + +# Precompile additional assets. +# application.js, application.css, and all non-JS/CSS in the app/assets +# folder are already added. +# Rails.application.config.assets.precompile += %w( admin.js admin.css ) diff --git a/config/initializers/backtrace_silencers.rb b/config/initializers/backtrace_silencers.rb new file mode 100644 index 0000000000..59385cdf37 --- /dev/null +++ b/config/initializers/backtrace_silencers.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. +# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } + +# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. +# Rails.backtrace_cleaner.remove_silencers! diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb new file mode 100644 index 0000000000..d3bcaa5ec8 --- /dev/null +++ b/config/initializers/content_security_policy.rb @@ -0,0 +1,25 @@ +# Be sure to restart your server when you modify this file. + +# Define an application-wide content security policy +# For further information see the following documentation +# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy + +# Rails.application.config.content_security_policy do |policy| +# policy.default_src :self, :https +# policy.font_src :self, :https, :data +# policy.img_src :self, :https, :data +# policy.object_src :none +# policy.script_src :self, :https +# policy.style_src :self, :https + +# # Specify URI for violation reports +# # policy.report_uri "/csp-violation-report-endpoint" +# end + +# If you are using UJS then enable automatic nonce generation +# Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) } + +# Report CSP violations to a specified URI +# For further information see the following documentation: +# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only +# Rails.application.config.content_security_policy_report_only = true diff --git a/config/initializers/cookies_serializer.rb b/config/initializers/cookies_serializer.rb new file mode 100644 index 0000000000..5a6a32d371 --- /dev/null +++ b/config/initializers/cookies_serializer.rb @@ -0,0 +1,5 @@ +# Be sure to restart your server when you modify this file. + +# Specify a serializer for the signed and encrypted cookie jars. +# Valid options are :json, :marshal, and :hybrid. +Rails.application.config.action_dispatch.cookies_serializer = :json diff --git a/config/initializers/filter_parameter_logging.rb b/config/initializers/filter_parameter_logging.rb new file mode 100644 index 0000000000..4a994e1e7b --- /dev/null +++ b/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Configure sensitive parameters which will be filtered from the log file. +Rails.application.config.filter_parameters += [:password] diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb new file mode 100644 index 0000000000..ac033bf9dc --- /dev/null +++ b/config/initializers/inflections.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, '\1en' +# inflect.singular /^(ox)en/i, '\1' +# inflect.irregular 'person', 'people' +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym 'RESTful' +# end diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb new file mode 100644 index 0000000000..dc1899682b --- /dev/null +++ b/config/initializers/mime_types.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Add new mime types for use in respond_to blocks: +# Mime::Type.register "text/richtext", :rtf diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb new file mode 100644 index 0000000000..fd4416122a --- /dev/null +++ b/config/initializers/omniauth.rb @@ -0,0 +1,3 @@ +Rails.application.config.middleware.use OmniAuth::Builder do + provider :github, ENV["GITHUB_CLIENT_ID"], ENV["GITHUB_CLIENT_SECRET"], scope: "user:email" +end diff --git a/config/initializers/wrap_parameters.rb b/config/initializers/wrap_parameters.rb new file mode 100644 index 0000000000..bbfc3961bf --- /dev/null +++ b/config/initializers/wrap_parameters.rb @@ -0,0 +1,14 @@ +# Be sure to restart your server when you modify this file. + +# This file contains settings for ActionController::ParamsWrapper which +# is enabled by default. + +# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. +ActiveSupport.on_load(:action_controller) do + wrap_parameters format: [:json] +end + +# To enable root element in JSON for ActiveRecord objects. +# ActiveSupport.on_load(:active_record) do +# self.include_root_in_json = true +# end diff --git a/config/locales/en.yml b/config/locales/en.yml new file mode 100644 index 0000000000..decc5a8573 --- /dev/null +++ b/config/locales/en.yml @@ -0,0 +1,33 @@ +# Files in the config/locales directory are used for internationalization +# and are automatically loaded by Rails. If you want to use locales other +# than English, add the necessary files in this directory. +# +# To use the locales, use `I18n.t`: +# +# I18n.t 'hello' +# +# In views, this is aliased to just `t`: +# +# <%= t('hello') %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# The following keys must be escaped otherwise they will not be retrieved by +# the default I18n backend: +# +# true, false, on, off, yes, no +# +# Instead, surround them with single quotes. +# +# en: +# 'true': 'foo' +# +# To learn more, please read the Rails Internationalization guide +# available at http://guides.rubyonrails.org/i18n.html. + +en: + hello: "Hello world" diff --git a/config/puma.rb b/config/puma.rb new file mode 100644 index 0000000000..a5eccf816b --- /dev/null +++ b/config/puma.rb @@ -0,0 +1,34 @@ +# Puma can serve each request in a thread from an internal thread pool. +# The `threads` method setting takes two numbers: a minimum and maximum. +# Any libraries that use thread pools should be configured to match +# the maximum value specified for Puma. Default is set to 5 threads for minimum +# and maximum; this matches the default thread size of Active Record. +# +threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } +threads threads_count, threads_count + +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. +# +port ENV.fetch("PORT") { 3000 } + +# Specifies the `environment` that Puma will run in. +# +environment ENV.fetch("RAILS_ENV") { "development" } + +# Specifies the number of `workers` to boot in clustered mode. +# Workers are forked webserver processes. If using threads and workers together +# the concurrency of the application would be max `threads` * `workers`. +# Workers do not work on JRuby or Windows (both of which do not support +# processes). +# +# workers ENV.fetch("WEB_CONCURRENCY") { 2 } + +# Use the `preload_app!` method when specifying a `workers` number. +# This directive tells Puma to first boot the application and load code +# before forking the application. This takes advantage of Copy On Write +# process behavior so workers use less memory. +# +# preload_app! + +# Allow puma to be restarted by `rails restart` command. +plugin :tmp_restart diff --git a/config/routes.rb b/config/routes.rb new file mode 100644 index 0000000000..7c6ad1d6cf --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,26 @@ +Rails.application.routes.draw do + root 'products#root' + + get "/auth/:provider/callback", to: "sessions#login", as: "auth_callback" + delete "/logout", to: "sessions#destroy", as: "logout" + get "users/:id/dashboard", to: "users#dashboard", as: "dashboard" + get "users/:user_id/products", to: "users#products", as: "merchant_products" + get "orders/:order_id/confirmation", to: "orders#confirmation", as: "confirmation" + + resources :users, only: [ :new, :create, :index] + + get '/sessions/current_order', to: "sessions#current_order", as: "current_order" + + resources :products, except: [:destroy] do + post 'retire', to: "products#retire", as: "retire" + post 'add_to_cart', to: "products#add_to_cart", as: "add_to_cart" + end + + resources :categories, only: [ :new, :create, :index , :show] + + resources :orders, only: [:show, :update, :edit] do + resources :orders_items, only: [:create, :new, :destroy,:update] + end + + +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_products.csv b/db/categories_products.csv new file mode 100644 index 0000000000..0c0daa1b70 --- /dev/null +++ b/db/categories_products.csv @@ -0,0 +1,20 @@ +product_id,category_id +1,2 +1,2 +1,3 +2,4 +2,3 +3,4 +4,5 +5,6 +5,7 +6,7 +6,2 +7,3 +8,1 +8,4 +9,5 +10,7 +11,2 +11,4 +5,3 diff --git a/db/category_seeds_3.csv b/db/category_seeds_3.csv new file mode 100644 index 0000000000..97b87a8ea5 --- /dev/null +++ b/db/category_seeds_3.csv @@ -0,0 +1,7 @@ +name +Ghost Siting +Alien Abductions +Cursed +Vampires +Werewolves +Famous Scary Houses \ No newline at end of file diff --git a/db/migrate/20181018012217_create_categories.rb b/db/migrate/20181018012217_create_categories.rb new file mode 100644 index 0000000000..6ccc3914a0 --- /dev/null +++ b/db/migrate/20181018012217_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/20181018013548_create_products.rb b/db/migrate/20181018013548_create_products.rb new file mode 100644 index 0000000000..5ae1a20fb2 --- /dev/null +++ b/db/migrate/20181018013548_create_products.rb @@ -0,0 +1,13 @@ +class CreateProducts < ActiveRecord::Migration[5.2] + def change + create_table :products do |t| + t.string :name + t.decimal :price + t.string :description + t.string :photo + t.integer :stock + + t.timestamps + end + end +end diff --git a/db/migrate/20181018014144_create_categorys_products_join.rb b/db/migrate/20181018014144_create_categorys_products_join.rb new file mode 100644 index 0000000000..03f743338a --- /dev/null +++ b/db/migrate/20181018014144_create_categorys_products_join.rb @@ -0,0 +1,8 @@ +class CreateCategorysProductsJoin < 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/20181018014707_create_orders.rb b/db/migrate/20181018014707_create_orders.rb new file mode 100644 index 0000000000..ac0f931eea --- /dev/null +++ b/db/migrate/20181018014707_create_orders.rb @@ -0,0 +1,9 @@ +class CreateOrders < ActiveRecord::Migration[5.2] + def change + create_table :orders do |t| + t.string :status + + t.timestamps + end + end +end diff --git a/db/migrate/20181018020046_create_users.rb b/db/migrate/20181018020046_create_users.rb new file mode 100644 index 0000000000..aa902609f0 --- /dev/null +++ b/db/migrate/20181018020046_create_users.rb @@ -0,0 +1,19 @@ +class CreateUsers < ActiveRecord::Migration[5.2] + def change + create_table :users do |t| + t.string :name + t.string :email + t.string :street + t.string :city + t.string :state + t.integer :zip + t.integer :creditcard + t.date :ccexpiration + t.integer :cvv + t.integer :billingzip + t.string :photo + + t.timestamps + end + end +end diff --git a/db/migrate/20181018020214_create_orders_items_join.rb b/db/migrate/20181018020214_create_orders_items_join.rb new file mode 100644 index 0000000000..faa57e4304 --- /dev/null +++ b/db/migrate/20181018020214_create_orders_items_join.rb @@ -0,0 +1,8 @@ +class CreateOrdersItemsJoin < ActiveRecord::Migration[5.2] + def change + create_table :orders_items do |t| + t.belongs_to :order, index: true, foreign_key: true + t.belongs_to :product, index: true, foreign_key: true + end + end +end diff --git a/db/migrate/20181018220858_move_usercolumnsto_order.rb b/db/migrate/20181018220858_move_usercolumnsto_order.rb new file mode 100644 index 0000000000..a889115ccd --- /dev/null +++ b/db/migrate/20181018220858_move_usercolumnsto_order.rb @@ -0,0 +1,25 @@ +class MoveUsercolumnstoOrder < ActiveRecord::Migration[5.2] + def change + #Removing the fields from user and adding them to the orders db and we are also adding the Oauth fields to User + remove_column :users,:street + remove_column :users,:city + remove_column :users,:state + remove_column :users,:zip + remove_column :users,:creditcard + remove_column :users,:ccexpiration + remove_column :users,:cvv + remove_column :users,:billingzip + add_column :users, :uid, :integer, null: false + add_column :users, :provider, :string, null: false + + #User info is stored to an order + add_column :orders, :street, :string + add_column :orders, :city, :string + add_column :orders, :state, :string + add_column :orders, :zip, :integer + add_column :orders, :creditcard, :integer + add_column :orders, :ccexpiration, :date + add_column :orders, :cvv, :integer + add_column :orders, :billingzip, :integer + end +end diff --git a/db/migrate/20181019011642_removerequirementfor_usercolumns.rb b/db/migrate/20181019011642_removerequirementfor_usercolumns.rb new file mode 100644 index 0000000000..19cf142838 --- /dev/null +++ b/db/migrate/20181019011642_removerequirementfor_usercolumns.rb @@ -0,0 +1,6 @@ +class RemoverequirementforUsercolumns < ActiveRecord::Migration[5.2] + def change + change_column :users, :uid, :integer, optional: true + change_column :users, :provider, :string, optional: true + end +end diff --git a/db/migrate/20181019012707_remvoing.rb b/db/migrate/20181019012707_remvoing.rb new file mode 100644 index 0000000000..605c7ac131 --- /dev/null +++ b/db/migrate/20181019012707_remvoing.rb @@ -0,0 +1,6 @@ +class Remvoing < ActiveRecord::Migration[5.2] + def change + remove_column :users, :uid + remove_column :users, :provider + end +end diff --git a/db/migrate/20181019012938_add_uidand_providerto_useras_optional.rb b/db/migrate/20181019012938_add_uidand_providerto_useras_optional.rb new file mode 100644 index 0000000000..d2f3849788 --- /dev/null +++ b/db/migrate/20181019012938_add_uidand_providerto_useras_optional.rb @@ -0,0 +1,6 @@ +class AddUidandProvidertoUserasOptional < ActiveRecord::Migration[5.2] + def change + add_column :users, :uid, :integer, optional: true + add_column :users, :provider, :string, optional: true + end +end diff --git a/db/migrate/20181019013807_fk_userto_product.rb b/db/migrate/20181019013807_fk_userto_product.rb new file mode 100644 index 0000000000..2c236d90bb --- /dev/null +++ b/db/migrate/20181019013807_fk_userto_product.rb @@ -0,0 +1,5 @@ +class FkUsertoProduct < ActiveRecord::Migration[5.2] + def change + add_reference :products, :user, foreign_key: true + end +end diff --git a/db/migrate/20181019021239_add_quantity_to_orders_items.rb b/db/migrate/20181019021239_add_quantity_to_orders_items.rb new file mode 100644 index 0000000000..f921a13388 --- /dev/null +++ b/db/migrate/20181019021239_add_quantity_to_orders_items.rb @@ -0,0 +1,5 @@ +class AddQuantityToOrdersItems < ActiveRecord::Migration[5.2] + def change + add_column :orders_items, :quantity, :integer + end +end diff --git a/db/migrate/20181021213645_fk_orderand_users.rb b/db/migrate/20181021213645_fk_orderand_users.rb new file mode 100644 index 0000000000..3d434f991a --- /dev/null +++ b/db/migrate/20181021213645_fk_orderand_users.rb @@ -0,0 +1,5 @@ +class FkOrderandUsers < ActiveRecord::Migration[5.2] + def change + add_reference :orders, :user, foreign_key: true + end +end diff --git a/db/migrate/20181026012310_add_name_and_email_to_order.rb b/db/migrate/20181026012310_add_name_and_email_to_order.rb new file mode 100644 index 0000000000..95c6dec388 --- /dev/null +++ b/db/migrate/20181026012310_add_name_and_email_to_order.rb @@ -0,0 +1,6 @@ +class AddNameAndEmailToOrder < ActiveRecord::Migration[5.2] + def change + add_column :orders, :name, :string + add_column :orders, :email, :string + end +end diff --git a/db/order_item_seeds.csv b/db/order_item_seeds.csv new file mode 100644 index 0000000000..bdae36c834 --- /dev/null +++ b/db/order_item_seeds.csv @@ -0,0 +1,13 @@ +order_id,product_id,quantity +1,1,2 +2,2,1 +3,3,4 +4,4,1 +5,5,1 +6,6,1 +7,7,1 +8,8,1 +9,9,1 +10,10,1 +11,11,1 +12,1,1 diff --git a/db/order_seeds_3.csv b/db/order_seeds_3.csv new file mode 100644 index 0000000000..cda2675a1a --- /dev/null +++ b/db/order_seeds_3.csv @@ -0,0 +1,13 @@ +street,city,state,zip,creditcard,ccexpiration,cvv,billingzip,status,user_id +1194 Upton Avenue,Brewer,ME,04412,4444,02/2018,098,05512,pending,1 +4218 Tennessee Avenue,Southfield,MI,48034,4445,04/2021,078,98102,paid,1 +3574 Raintree Boulevard,,IN,47905,4446,06/2019,634,47905,complete,2 +976 Kidd Avenue,Anchorage,AK,99501,4447,02/2010,235,78912,cancelled,2 +3953 Randall Drive,Honolulu,HI,96819,4448,07/2022,511,47905,pending,3 +527 Hudson Street,Passaic,NJ,07055,4449,09/2010,702,98345,paid,3 +2159 Farm Meadow Drive,Yuma,AZ,85364,4450,03/2022,823,12745,complete,4 +1867 Byers Lane,Hamilton City,CA,95951,4451,11/2021,561,47905,cancelled,4 +3613 Mercer Street,Wausau,WI,54403,4452,12/2021,772,45221,complete,5 +3257 Briarwood Road,Humansville,MO,65674,4453,11/2018,102,47905,paid,6 +247 Northwest Boulevard,Fort Lee,NJ,07024,4454,3/2021,871,80503,pending,6 +3434 Northwest Boulevard,Fort Lee,NJ,07024,4454,3/2021,871,80503,paid,3 diff --git a/db/product_seeds_3.csv b/db/product_seeds_3.csv new file mode 100644 index 0000000000..eb700d0c09 --- /dev/null +++ b/db/product_seeds_3.csv @@ -0,0 +1,13 @@ +name,price,description,photo,stock,user_id +Graveyard,200000,"This large house looks a bit old-fashioned and is in poor condition. The interior is done in colors that remind you of a thunderstorm. The yard is moderately-sized and looks very formal. Also, rumor has it that a group of thieves used it as a meeting place.",https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSsFTbW1HRdHQZvmaFy2q107xa8mNegOzY_t5gLuP_FPIp9Me94Qw,1,1 +Amityville Horror,1000000,"Super spooky Amityvilled Horror",https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRnHFsC4-sPGTFFgUOO1laemwdUXUL4iBRBaf9GuwtIk3i_vKU0,3,1 +American Horror Story Season 1 House,3000000,"This large house has a retro look to it and is in terrible condition. The interior is done in colors that remind you of an old book. The yard is large and resembles a creepy forest. Also, it's rumored that an old man who went inside never came out.",https://timedotcom.files.wordpress.com/2018/02/180213-american-horror-story-haunted-house.jpg?quality=85,10,1 +Copper King Mansion,250000,"This enormous house looks a bit old-fashioned and is in poor condition. The interior is done in faded colors. The yard is small and is severely overgrown. Also, it's rumored to be built over an old burial ground.",https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcT_zyYqoTuVbsINmagYKaHsCwIKrg6ZDJHclmatkBs8h__oSuuh,1,2 +Haught Mansion,3500000,"Creepy mansion in Michigan",https://amp.businessinsider.com/images/562fbe129dd7cc01308b9b3b-640-514.jpg,2,2 +Nova House,2500000,"This large house has a retro look to it and is in excellent condition. The interior is done in colors that remind you of an old newspaper. The yard is enormous and is full of dead plants. Also, its last occupants left in a hurry.",https://petapixel.com/assets/uploads/2014/10/ohio2.jpg,3,3 +Temple Mansion,425000000,"This enormous house looks like an old castle and is in terrible condition. The interior is done in colors that remind you of an old ship. The yard is moderately-sized and resembles a meadow. Also, it's rumored to be built over an old cemetery.",https://petapixel.com/assets/uploads/2014/10/michigan2.jpg,2,4 +The Sallie House,3500000,"This small house looks very modern and is in terrible condition. The interior is done in colors that remind you of an old ship. The yard is small and is neatly-trimmed. Also, it's the site of an infamous murder.",https://petapixel.com/assets/uploads/2014/10/ohio.jpg,3,3 +LaLaurie House,2300000,"This moderately-sized house looks very old-fashioned and is in poor condition. The interior is done in drab colors. The yard is tiny and is overgrown with wild plants. Also, its owners have a record of dying in strange and unforeseeable accidents.",https://petapixel.com/assets/uploads/2014/10/connecticut.jpg,2,4 +Bell Witch Farm,3000000,"This enormous house has a quaint atmosphere and is in terrible condition. The interior is done in colors that remind you of the night sky. The yard is large and is severely overgrown. Also, an old man was murdered here a long time ago.",https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSMGr0RkStGT5CrPiz3gfvgI_VACaggzhajcF7_JigRWZ_9vg0Z-g,1,5 +Super Spooky Farm,3000000,"This house has a creepy atmosphere and is in fantastic condition. The interior is done in colors that remind you of winter. Also, rumor has it a ghost haunts the basement",https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSMGr0RkStGT5CrPiz3gfvgI_VACaggzhajcF7_JigRWZ_9vg0Z-g,1,5 +Haunted Mansion,4000000,"This enormous mansion has a creepy atmosphere and is in horrid condition. The interior is done in colors that remind you of the night sky. The yard is large and is severely overgrown. Also, an old man was murdered here a long time ago.",https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSMGr0RkStGT5CrPiz3gfvgI_VACaggzhajcF7_JigRWZ_9vg0Z-g,1,5 diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 0000000000..da00e58b0a --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,83 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# Note that this schema.rb definition is the authoritative source for your +# database schema. If you need to create the application database on another +# system, you should be using db:schema:load, not running all the migrations +# from scratch. The latter is a flawed and unsustainable approach (the more migrations +# you'll amass, the slower it'll run and the greater likelihood for issues). +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema.define(version: 2018_10_26_012310) 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 "orders", force: :cascade do |t| + t.string "status" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "street" + t.string "city" + t.string "state" + t.integer "zip" + t.integer "creditcard" + t.date "ccexpiration" + t.integer "cvv" + t.integer "billingzip" + t.bigint "user_id" + t.string "name" + t.string "email" + t.index ["user_id"], name: "index_orders_on_user_id" + end + + create_table "orders_items", force: :cascade do |t| + t.bigint "order_id" + t.bigint "product_id" + t.integer "quantity" + t.index ["order_id"], name: "index_orders_items_on_order_id" + t.index ["product_id"], name: "index_orders_items_on_product_id" + end + + create_table "products", force: :cascade do |t| + t.string "name" + t.decimal "price" + t.string "description" + t.string "photo" + t.integer "stock" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.bigint "user_id" + t.index ["user_id"], name: "index_products_on_user_id" + end + + create_table "users", force: :cascade do |t| + t.string "name" + t.string "email" + t.string "photo" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "uid" + t.string "provider" + end + + add_foreign_key "orders", "users" + add_foreign_key "orders_items", "orders" + add_foreign_key "orders_items", "products" + add_foreign_key "products", "users" +end diff --git a/db/seeds.rb b/db/seeds.rb new file mode 100644 index 0000000000..6ab9b5aa3e --- /dev/null +++ b/db/seeds.rb @@ -0,0 +1,171 @@ +# 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' + + +CATEGORY_FILE = Rails.root.join('db', 'category_seeds_3.csv') +puts "Loading raw category data from #{CATEGORY_FILE}" + +category_failures = [] +CSV.foreach(CATEGORY_FILE, :headers => true) do |row| + category = Category.new + category.name = row['name'] + successful = category.save + if !successful + category_failures << category + puts "Failed to save categories: #{category.inspect}" + else + puts "Created categories: #{category.inspect}" + end +end + +puts "Added #{Category.count} category records" +puts "#{category_failures.length} category failed to save" + +#-------------------------------------------------------- + +USER_FILE = Rails.root.join('db', 'user_seeds_3.csv') +puts "Loading raw user data from #{USER_FILE}" + +user_failures = [] +CSV.foreach(USER_FILE, :headers => true) do |row| + user = User.new + user.name = row['name'] + user.email = row['email'] + user.photo = row['photo'] + user.uid = row['uid'] + user.provider = row['provider'] + successful = user.save + if !successful + user_failures << user + puts "Failed to save users: #{user.inspect}" + else + puts "Created users: #{user.inspect}" + end +end + +puts "Added #{User.count} user records" +puts "#{user_failures.length} user failed to save" + +#-------------------------------------------------------- + +ORDERS_FILE = Rails.root.join('db', 'order_seeds_3.csv') +puts "Loading raw order data from #{ORDERS_FILE}" + +order_failures = [] +CSV.foreach(ORDERS_FILE, :headers => true) do |row| + order= Order.new + order.status = row['status'] + order.street = row['street'] + order.city = row['city'] + order.state = row['state'] + order.zip = row['zip'] + order.creditcard = row['creditcard'] + order.ccexpiration = Date.parse(row['ccexpiration']) + order.cvv = row['cvv'] + order.billingzip = row['billingzip'] + successful = order.save + if !successful + order_failures << order + puts "Failed to save orders: #{order.inspect}" + else + puts "Created orders: #{order.inspect}" + end +end + +puts "Added #{Order.count} order records" +puts "#{order_failures.length} order failed to save" + +#-------------------------------------------------------- + +PRODUCTS_FILE = Rails.root.join('db', 'product_seeds_3.csv') +puts "Loading raw product data from #{PRODUCTS_FILE}" + +product_failures = [] +CSV.foreach(PRODUCTS_FILE, :headers => true) do |row| + product = Product.new + product.name = row['name'] + product.price = row['price'] + product.description = row['description'] + product.photo = row['photo'] + product.stock = row['stock'] + product.user_id = row['user_id'] + # + # Add some categories + category_amount = [1,2,3,4].sample + category_amount.times do + category_id = (1..Category.all.length).to_a.sample + category = Category.find_by(id: category_id) + # This is making it get stuck for some reason + # Is not converting range into an array for each number + while product.categories.include?(category) + category_id = ( 1..Category.all.length ).to_a.sample + category = Category.find_by(id: category_id) + end + product.categories << category + end + # + successful = product.save + if !successful + product_failures << product + puts "Failed to save products: #{product.inspect}" + else + puts "Created products: #{product.inspect}" + end +end + +puts "Added #{Product.count} product records" +puts "#{product_failures.length} product failed to save" + +#-------------------------------------------------------- + +ORDER_ITEMS_FILE = Rails.root.join('db', 'order_item_seeds.csv') +puts "Loading raw product data from #{ORDER_ITEMS_FILE}" + +order_items_failures = [] +CSV.foreach(ORDER_ITEMS_FILE, :headers => true) do |row| + order_item = OrdersItem.new + order_item.order_id = row['order_id'] + order_item.product_id = row['product_id'] + order_item.quantity = row['quantity'] + successful = order_item.save + if !successful + order_items_failures << order_item + puts "Failed to save order items: #{order_item.inspect}" + else + puts "Created order items: #{order_item.inspect}" + end +end + +puts "Added #{OrdersItem.count} order item records" +puts "#{order_items_failures.length} order items failed to save" + + + +#-------------------------------------------------------- + +# CATEGORIES_PRODUCTS_FILE = Rails.root.join('db', 'order_item_seeds.csv') +# puts "Loading raw product data from #{CATEGORIES_PRODUCTS_FILE}" +# +# cat_items_failures = [] +# CSV.foreach(CATEGORIES_PRODUCTS_FILE, :headers => true) do |row| +# category_product = CategoryProduct.new +# order_item.order_id = row['order_id'] +# order_item.product_id = row['product_id'] +# order_item.quantity = row['quantity'] +# successful = order_item.save +# if !successful +# order_items_failures << order_item +# puts "Failed to save order items: #{order_item.inspect}" +# else +# puts "Created order items: #{order_item.inspect}" +# end +# end +# +# puts "Added #{OrdersItem.count} order item records" +# puts "#{order_items_failures.length} order items failed to save" diff --git a/db/user_seeds_3.csv b/db/user_seeds_3.csv new file mode 100644 index 0000000000..94f17d6179 --- /dev/null +++ b/db/user_seeds_3.csv @@ -0,0 +1,9 @@ +name,email,photo,uid,provider +Sandi Metz,smetz@gmail.com,https://static1.squarespace.com/static/537c0374e4b0f52ed92942e6/t/5837a98e03596ef406e26562/1480042911921/DSC_3772.jpg,234343,github +N K Jemisin,nkjemison@gmail.com,"https://www.popsci.com/sites/popsci.com/files/styles/1000_1x_/public/images/2017/01/zombie.jpg?itok=n9MHuOy4&fc=50,50",34543545,github +Nnedi Okorafor,nokorafor@gmaill.com,https://www.google.com/url?sa=i&source=images&cd=&cad=rja&uact=8&ved=2ahUKEwi2qqaUp5HeAhUxNX0KHdDcAxIQjRx6BAgBEAU&url=https%3A%2F%2Fwww.opticsplanet.com%2Fgearexpert%2F2017%2F10%2Fthe-best-9-products-to-keep-you-from-getting-eaten-by-a-werewolf.html&psig=AOvVaw1c-Kp0Cbh4ky81mWKVRU1S&ust=1539997260154425,6546456,github +Roxane Gay,rgay@gmaill.com,https://s7.orientaltrading.com/is/image/OrientalTrading/PDP_VIEWER_IMAGE_MOBILE$&$NOWA/life-size-posable-skeleton~13703399,654646,github +JK Rowling,jkrowling@gmaill.com,https://images-na.ssl-images-amazon.com/images/S/amzn-author-media-prod/pkhkqpju402jii5i53t7vru84r._UX250_.jpg,6465468,github +Margot Lee Shetterly,mlshetterly@gmaill.com,http://r.ddmcdn.com/s_f/o_1/DAM/uploads/2017/03/ghost-girl-street.jpg,6451232,github +Jane Doe,jane@gmail.com,https://cdn2.iconfinder.com/data/icons/avatar-2/512/iri_girl_face-512.png +John Doe,john@hotmail.com,https://proxy.duckduckgo.com/iu/?u=https%3A%2F%2Fi.pinimg.com%2F736x%2F3f%2Ffd%2Fb0%2F3ffdb08aafb723e161722847da87a058--male-man-avatar.jpg&f=1 diff --git a/formhelp.html b/formhelp.html new file mode 100644 index 0000000000..b68a75cdf8 --- /dev/null +++ b/formhelp.html @@ -0,0 +1,251 @@ + + +
+
+ +

Checkout form

+

Below is an example form built entirely with Bootstrap's form controls. Each required form group has a validation state that can be triggered by attempting to submit the form without completing it.

+
+ +
+
+

+ Your cart + 3 +

+
    +
  • +
    +
    Product name
    + Brief description +
    + $12 +
  • +
  • +
    +
    Second product
    + Brief description +
    + $8 +
  • +
  • +
    +
    Third item
    + Brief description +
    + $5 +
  • +
  • +
    +
    Promo code
    + EXAMPLECODE +
    + -$5 +
  • +
  • + Total (USD) + $20 +
  • +
+ +
+
+ +
+ +
+
+
+
+
+

Billing address

+
+
+
+ + +
+ Valid first name is required. +
+
+
+ + +
+ Valid last name is required. +
+
+
+ +
+ +
+
+ @ +
+ +
+ Your username is required. +
+
+
+ +
+ + +
+ Please enter a valid email address for shipping updates. +
+
+ +
+ + +
+ Please enter your shipping address. +
+
+ +
+ + +
+ +
+
+ + +
+ Please select a valid country. +
+
+
+ + +
+ Please provide a valid state. +
+
+
+ + +
+ Zip code required. +
+
+
+
+
+ + +
+
+ + +
+
+ +

Payment

+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + + Full name as displayed on card +
+ Name on card is required +
+
+
+ + +
+ Credit card number is required +
+
+
+
+
+ + +
+ Expiration date required +
+
+
+ + +
+ Security code required +
+
+
+
+ +
+
+
+ + +
+ + + + + + + + + + + + diff --git a/lib/assets/.keep b/lib/assets/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/tasks/.keep b/lib/tasks/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/log/.keep b/log/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/package.json b/package.json new file mode 100644 index 0000000000..f874acf437 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "name": "betsy", + "private": true, + "dependencies": {} +} diff --git a/public/404.html b/public/404.html new file mode 100644 index 0000000000..2be3af26fc --- /dev/null +++ b/public/404.html @@ -0,0 +1,67 @@ + + + + The page you were looking for doesn't exist (404) + + + + + + +
+
+

The page you were looking for doesn't exist.

+

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

+
+

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

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

The change you wanted was rejected.

+

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

+
+

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

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

We're sorry, but something went wrong.

+
+

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

+
+ + diff --git a/public/apple-touch-icon-precomposed.png b/public/apple-touch-icon-precomposed.png new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000000..37b576a4a0 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1 @@ +# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file diff --git a/storage/.keep b/storage/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/application_system_test_case.rb b/test/application_system_test_case.rb new file mode 100644 index 0000000000..d19212abd5 --- /dev/null +++ b/test/application_system_test_case.rb @@ -0,0 +1,5 @@ +require "test_helper" + +class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + driven_by :selenium, using: :chrome, screen_size: [1400, 1400] +end diff --git a/test/controllers/.keep b/test/controllers/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/controllers/categories_controller_test.rb b/test/controllers/categories_controller_test.rb new file mode 100644 index 0000000000..aa3aff960d --- /dev/null +++ b/test/controllers/categories_controller_test.rb @@ -0,0 +1,111 @@ +require "test_helper" +describe CategoriesController do + it "should get index" do + get categories_path + must_respond_with :success + end + + describe "new" do + it "can get the new category page" do + + @user = users(:tom) + # Make fake session + # Tell OmniAuth to use this user's info when it sees + # an auth callback from github + OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new(mock_auth_hash(@user)) + get auth_callback_path('github') + + get new_category_path + + must_respond_with :success + end + end + + describe "create " do + let (:category_hash) do + { + category: { + name: 'Werewolf' + } + } + end + + + it "can create a new category given valid params if logged in" do + @user = users(:tom) + # Make fake session + # Tell OmniAuth to use this user's info when it sees + # an auth callback from github + OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new(mock_auth_hash(@user)) + get auth_callback_path('github') + + # Act-Assert + expect { + post categories_path, params: category_hash + }.must_change 'Category.count', 1 + + must_respond_with :redirect + must_redirect_to dashboard_path(@user.id) + + expect(Category.last.name).must_equal category_hash[:category][:name] + + end + + it "does not allow guests to access the new category page" do + user = users(:guest) + get new_category_path + expect(flash[:danger]).must_equal "You must be logged in to view this section" + must_redirect_to root_path + + end + + it "responds with an error for invalid params" do + + @user = users(:tom) + # Make fake session + # Tell OmniAuth to use this user's info when it sees + # an auth callback from github + OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new(mock_auth_hash(@user)) + get auth_callback_path('github') + + category_hash[:category][:name] = nil + + expect { + post categories_path, params: category_hash + }.wont_change 'Category.count' + + must_respond_with :bad_request + end + + end #end of create describe block + + describe "show" do + it "should get a category's show page" do + id = categories(:category1).id + + + get category_path(id) + + must_respond_with :success + end + + it "should get a not found page when category doesn't exist" do + id = -2 + + get category_path(id) + + must_respond_with :not_found + end + end + +end #end of categoriescontroller do + + +# def show +# @category = Category.find_by(id: params[:id]) +# +# if @cateogry.nil? +# flash.now[:danger] = "Cannot find the category #{params[:id]}" +# render :notfound, status: :not_found +# end +# end diff --git a/test/controllers/orders_controller_test.rb b/test/controllers/orders_controller_test.rb new file mode 100644 index 0000000000..d9c9f1443f --- /dev/null +++ b/test/controllers/orders_controller_test.rb @@ -0,0 +1,110 @@ +require "test_helper" + +describe OrdersController do + + describe " update" do + let (:order_hash) do + { + order: { + status: "pending", street: "884 Ada Academy", city: "seattle", state: "WA", zip: 98112, + creditcard: 1234, cvv: 234, billingzip: 98112, ccexpiration: Date.today + } + } + end + + + describe "update" do + it "will create a new order with the passed params if the order isn't @current_order and change order status from pending to paid" do + order = orders(:one) + expect { + patch order_path(order.id), params: order_hash + }.must_change 'Order.count', 1 + + order = Order.all.last + must_respond_with :redirect + must_redirect_to confirmation_path(order.id) + + expect(order.status).must_equal "paid" + expect(order.street).must_equal order_hash[:order][:street] + expect(order.city).must_equal order_hash[:order][:city] + expect(order.state).must_equal order_hash[:order][:state] + expect(order.zip).must_equal order_hash[:order][:zip] + expect(order.creditcard).must_equal order_hash[:order][:creditcard] + expect(order.cvv).must_equal order_hash[:order][:cvv] + expect(order.billingzip).must_equal order_hash[:order][:billingzip] + end + + + it "should get an order's show page" do + #Arrange + id = orders(:one).id + #Act + get order_path(id) + #Assert + must_respond_with :success + end + + it "should respond with not_found if given an invalid id" do + #asrrange - invalid id + id = -1 + #Act + get order_path(id) + #Assert + must_respond_with :not_found + expect(flash[:danger]).must_equal "Cannot find the order -1" + end + # + # # + describe "edit" do + it "can get the edit page for a valid order" do + # Arrange + id = orders(:one).id + + # Act + get edit_order_path(id) + + # Assert + must_respond_with :success + end + + it "should respond with not_found if given an invalid id" do + # Arrange - invalid id + id = -1 + # Act + get edit_order_path(id) + + # Assert + expect(response).must_be :not_found? + must_respond_with :not_found + expect(flash[:danger]).must_equal "Cannot find the order -1" + end + end + + end + end + + describe "show" do + + it "should get an order's show page" do + #Arrange + id = orders(:one).id + #Act + get order_path(id) + #Assert + must_respond_with :success + end + + it "should respond with not_found if given an invalid id" do + #asrrange - invalid id + id = -1 + #Act + get order_path(id) + #Assert + must_respond_with :not_found + expect(flash[:danger]).must_equal "Cannot find the order -1" + end + + end + + + end diff --git a/test/controllers/orders_items_controller_test.rb b/test/controllers/orders_items_controller_test.rb new file mode 100644 index 0000000000..251e767e80 --- /dev/null +++ b/test/controllers/orders_items_controller_test.rb @@ -0,0 +1,18 @@ +require "test_helper" + +describe OrdersItemsController do + # it "should get new" do + # get new_order_path + # value(response).must_be :success? + # end + + + it "can add new items to an order" do + # "order_id" = $1 [["order_id", 193]] + # => [#, + # #] + end + + it "can remove new items to an order" do + end +end diff --git a/test/controllers/products_controller_test.rb b/test/controllers/products_controller_test.rb new file mode 100644 index 0000000000..13678e40f6 --- /dev/null +++ b/test/controllers/products_controller_test.rb @@ -0,0 +1,206 @@ +require "test_helper" + + +describe ProductsController do + it "should get index" do + get products_path + must_respond_with :success + end + + describe "show" do + it "should get a product's show page" do + id = products(:product1).id + + get product_path(id) + + must_respond_with :success + end + + it "should respond with not_found if given an invalid id" do + id = -1 + + get product_path(id) + + must_respond_with :not_found + expect(flash[:danger]).must_equal "Cannot find the product -1" + end + end + + describe "actions that require User Authentication" do + + before do + user = users(:tom) + # Make fake session + # Tell OmniAuth to use this user's info when it sees + # an auth callback from github + OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new(mock_auth_hash(user)) + get auth_callback_path('github') + end + + let (:product_hash) do + { + product: { + name: 'Forbidden Forest', + price: 8888, + description: 'scary forest that no one is allowed to go to', + photo: 'https://images.google.com/', + stock: 1, + user_id: users(:tom).id + } + } + end + + describe "edit" do + it "can get the edit page for a valid product" do + # Arrange + id = products(:product1).id + + # Act + get edit_product_path(id) + + # Assert + must_respond_with :success + end + + it "should respond with not_found if given an invalid id" do + # Arrange - invalid id + id = -1 + + # Act + get edit_product_path(id) + + # Assert + expect(response).must_be :not_found? + must_respond_with :not_found + expect(flash[:danger]).must_equal "Cannot find the product -1" + end + end + + describe "create & update" do + + + describe "create" do + it "can create a new product given valid params" do + @merchant = users(:tom) + # Make fake session + # Tell OmniAuth to use this user's info when it sees + # an auth callback from github + OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new(mock_auth_hash(@merchant)) + get auth_callback_path('github') + + # Act-Assert + expect { + post products_path, params: product_hash + }.must_change 'Product.count', 1 + + must_respond_with :redirect + must_redirect_to dashboard_path(@merchant.id) #the last product bc this new one will be added to the end + + expect(Product.last.name).must_equal product_hash[:product][:name] + expect(Product.last.price).must_equal product_hash[:product][:price] + expect(Product.last.description).must_equal product_hash[:product][:description] + expect(Product.last.photo).must_equal product_hash[:product][:photo] + expect(Product.last.stock).must_equal product_hash[:product][:stock] + expect(Product.last.user_id).must_equal product_hash[:product][:user_id] + + end + + it "responds with an error for invalid params" do + + # Arranges + product_hash[:product][:name] = nil + + # Act-Assert + expect { + post products_path, params: product_hash + + }.wont_change 'Product.count' + + must_respond_with :bad_request + + end + end + + + describe "update" do + it "can update a model with valid params" do + id = products(:product1).id + + expect { + patch product_path(id), params: product_hash + }.wont_change 'Product.count' + + must_respond_with :redirect + must_redirect_to product_path(id) + + new_product = Product.find_by(id: id) + + expect(new_product.name).must_equal product_hash[:product][:name] + expect(new_product.price).must_equal product_hash[:product][:price] + expect(new_product.description).must_equal product_hash[:product][:description] + expect(new_product.photo).must_equal product_hash[:product][:photo] + expect(new_product.stock).must_equal product_hash[:product][:stock] + expect(new_product.user_id).must_equal product_hash[:product][:user_id] + end + it "gives an error if the product params are invalid" do + # Arrange + product_hash[:product][:name] = nil + id = products(:product1).id + old_product = products(:product1) + + + expect { + patch product_path(id), params: product_hash + }.wont_change 'Product.count' + new_product = Product.find(id) + + must_respond_with :bad_request + expect(old_product.name).must_equal new_product.name + expect(old_product.description).must_equal new_product.description + expect(old_product.photo).must_equal new_product.photo + expect(old_product.stock).must_equal new_product.stock + expect(old_product.user_id).must_equal new_product.user_id + end + it "gives not_found for a product that doesn't exist" do + id = -1 + + expect { + patch product_path(id), params: product_hash + }.wont_change 'Product.count' + + must_respond_with :not_found + end + + end + end + end + describe "add_to_cart" do + it "adds a prodcut to an order" do + + # Adding some products + # Arrange - + product_one = products(:product1) + product_two = products(:product2) + product_three = products(:product3) + + + + # Act / Assert + post product_add_to_cart_path(product_one.id) + post product_add_to_cart_path(product_two.id) + post product_add_to_cart_path(product_three.id) + # Current order is nil as there is no session + # Do we make our own session order? + current_order = Order.find(session[:order_id]) + current_order.products.length.must_equal 3 + current_order.products.first.must_equal product_one + current_order.products.all[1].must_equal product_two + current_order.products.last.must_equal product_three + end + + # Add some products - run post add_to_cart_path(product.id) + + + end + +end diff --git a/test/controllers/sessions_controller_test.rb b/test/controllers/sessions_controller_test.rb new file mode 100644 index 0000000000..c6ddf25c82 --- /dev/null +++ b/test/controllers/sessions_controller_test.rb @@ -0,0 +1,105 @@ +require "test_helper" + +describe SessionsController do + + describe "login" do + it "Can log in an existing user" do + + # Arrange + user = users(:grace) + + # Tell OmniAuth to use this user's info when it sees + # an auth callback from github + OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new(mock_auth_hash(user)) + # + # # Act + expect { + get auth_callback_path('github') + }.wont_change('User.count') + # + # # Assert + must_redirect_to root_path + expect(session[:user_id]).must_equal user.id + must_respond_with :redirect + end + + it "Can log in a new user with good data" do + # Arrange + user = users(:grace) + user.destroy + + # Tell OmniAuth to use this user's info when it sees + # an auth callback from github + OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new(mock_auth_hash(user)) + + # Act + expect { + get auth_callback_path('github') + }.must_change('User.count', +1) + + # Assert + must_respond_with :redirect + must_redirect_to root_path + expect(session[:user_id]).wont_be_nil + end + + it "Rejects a user with invalid data" do + # Is still being added :( + # Arrange + user = User.new( + name: "bob", uid: 234324343, provider: 'github' + ) + + # Tell OmniAuth to use this user's info when it sees + # an auth callback from github + OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new(mock_auth_hash(user)) + + # Making use have invalid data + # Act - Will not change user count + # Assert + # Won't add a user to database + expect { + get auth_callback_path('github') + }.wont_change("User.count") + + # Will flash an error - Can't save + user.save + expect(flash[:error]).must_equal "Could not create new user account: #{user.errors.messages}" + # And redirect to root_path + must_respond_with :redirect + must_redirect_to root_path + + end + end + + describe "logout" do + + it "can log a user out" do + # Arrange + # Creating user / logging user in + user = users(:grace) + OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new(mock_auth_hash(user)) + + # Logging user in + get auth_callback_path('github') + # start_count = User.count + + # Act - Logging user out + delete logout_path + # user.destroy - don't think we destroy the user + + # Assert + # Should set session usser id to nil + expect(session[:user_id]).must_be_nil + # Flash a message + expect(flash[:success]).must_equal "Successfully logged out!" + # And redirect to root_path + must_respond_with :redirect + must_redirect_to root_path + end + end + + + + + end diff --git a/test/controllers/users_controller_test.rb b/test/controllers/users_controller_test.rb new file mode 100644 index 0000000000..cac8f611b8 --- /dev/null +++ b/test/controllers/users_controller_test.rb @@ -0,0 +1,125 @@ +require "test_helper" + +describe UsersController do + + describe "index" do + it "should get index" do + get users_path + must_respond_with :success + end + end + + describe "products" do + it "Should show products for the merchant" do + user = users(:tom) + # Make fake session + # Tell OmniAuth to use this user's info when it sees + # an auth callback from github + OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new(mock_auth_hash(user)) + get auth_callback_path('github') + get merchant_products_path(user.id) + must_respond_with :success + end + + + + end + + + describe "dashboard" do + + it "guest users cannot access dashboard" do + # Setting to guest user + # The only person that can + user = users(:guest) + + # Setting a merchant + merchant = users(:dani) + # Attempting to access merchant 3 dashboard + + get dashboard_path(merchant.id) + # Expectation - Flash message/redirect + expect(flash[:danger]).must_equal "You must be logged in to view this section" + must_redirect_to root_path + end + + it "should get dashboard if you are a logged in merchant/it's your dashboard" do + # Arrange + user = users(:tom) + # Make fake session + # Tell OmniAuth to use this user's info when it sees + # an auth callback from github + OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new(mock_auth_hash(user)) + get auth_callback_path('github') + + # Should be logged in now.. + + # Act + + get dashboard_path(user.id) + # Assert + # Is not saving for some reason + must_respond_with :success + end + + + it "should respond with not_found if given an invalid id" do + # Invalid is anything but the merchant's id + # Arrange - Not tom's id + id = -1 + + # Act + get dashboard_path(id) + + #Assert + must_respond_with :not_found + expect(flash[:danger]).must_equal "Cannot find user" + end + end + + describe "create" do + let (:user_hash) do + { + user: { + name: 'Bob', + email: 'bob@gmail.com', + photo: 'bobsphoto' + } + } + end + + it "can create a new user given valid params" do + # Says it's redirect is a users . instead of users/, not sure why + + expect { + post users_path, params: user_hash + }.must_change 'User.count', 1 + + must_respond_with :redirect + must_redirect_to root_path + + expect(User.last.name).must_equal user_hash[:user][:name] + expect(User.last.email).must_equal user_hash[:user][:email] + + expect(User.last.photo).must_equal user_hash[:user][:photo] + expect(User.last.uid).must_equal user_hash[:user][:uid] + expect(User.last.provider).must_equal user_hash[:user][:provider] + end + + it "responds with an error for invalid params" do + # Arrange + user_hash[:user][:name] = nil + + #Act/Assert + expect { + post users_path, params: user_hash + }.wont_change 'User.count' + + # Says the count changed :( + + must_respond_with :redirect + must_redirect_to root_path + + 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..972d234f94 --- /dev/null +++ b/test/fixtures/categories.yml @@ -0,0 +1,29 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + + +haunted: + name: Haunted + +zombie: + name: Zombie + +category1: + name: Graveyard + +category2: + name: Forest + +category3: + name: Empty Plot of Land + +category4: + name: Gingerbread House + +category5: + name: Basement + +category6: + name: Bridge + +category7: + name: Alley diff --git a/test/fixtures/files/.keep b/test/fixtures/files/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/fixtures/orders.yml b/test/fixtures/orders.yml new file mode 100644 index 0000000000..5b2a7c3dbe --- /dev/null +++ b/test/fixtures/orders.yml @@ -0,0 +1,44 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + status: pending + street: 111 Yosemite + city: Seattle + state: WA + zip: 98122 + creditcard: 1000 + ccexpiration: Date.today + cvv: 111 + billingzip: 98122 + #products: graveyard + + +two: + status: paid + street: order 2 address + city: Seattle + state: WA + zip: 98133 + creditcard: 1111 + cvv: 123 + billingzip: 98133 + +three: + status: cancelled + street: order 3 address + city: Seattle + state: WA + zip: 98133 + creditcard: 1111 + cvv: 123 + billingzip: 98133 + +four: + status: complete + street: order 4 address + city: Seattle + state: WA + zip: 98133 + creditcard: 1111 + cvv: 123 + billingzip: 98133 diff --git a/test/fixtures/orders_items.yml b/test/fixtures/orders_items.yml new file mode 100644 index 0000000000..dd7305023e --- /dev/null +++ b/test/fixtures/orders_items.yml @@ -0,0 +1,14 @@ +item1: + order: one + product: product3 + quantity: 3 + +item2: + order: one + product: product2 + quantity: 1 + +item3: + order: two + product: product1 + quantity: 1 diff --git a/test/fixtures/products.yml b/test/fixtures/products.yml new file mode 100644 index 0000000000..7562ef8aa7 --- /dev/null +++ b/test/fixtures/products.yml @@ -0,0 +1,28 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +product1: + name: a product + price: 9.99 + description: great product + photo: https://i2.wp.com/beebom.com/wp-content/uploads/2016/01/Reverse-Image-Search-Engines-Apps-And-Its-Uses-2016.jpg?resize=640%2C426 + stock: 4 + user: kay + + +product2: + name: b product + price: 11 + description: medium product + photo: https://i2.wp.com/beebom.com/wp-content/uploads/2016/01/Reverse-Image-Search-Engines-Apps-And-Its-Uses-2016.jpg?resize=640%2C426 + stock: 2 + user: dani + + + +product3: + name: c product + price: 10.00 + description: small product + photo: https://i2.wp.com/beebom.com/wp-content/uploads/2016/01/Reverse-Image-Search-Engines-Apps-And-Its-Uses-2016.jpg?resize=640%2C426 + stock: 3 + user: kay diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml new file mode 100644 index 0000000000..4a66352a24 --- /dev/null +++ b/test/fixtures/users.yml @@ -0,0 +1,74 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + + + + +user1: + name: User1 + email: MyString + photo: MyString + uid: 67 + provider: github + +user2: + name: User2 + email: MyString@gmail.com + photo: MyString + uid: 1031 + provider: github + +user3: + name: User3 + email: MyString@gmail.com + photo: MyString + uid: 889 + provider: github + +dani: + name: dani + email: dani@gmail.com + photo: MyString + uid: 111 + provider: github + +kay: + name: kay + email: kay@gmail.com + photo: MyString + uid: 222 + provider: github + + # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html +grace: + name: grace + email: s@s.com + photo: MyString + uid: 343434234 + provider: github + +john: + name: john + email: sdf@sdf.com + photo: johnsphoto + uid: 542345234 + provider: github + +user1: + name: user1 + email: user2@gmail.com + photo: MyString + uid: 45646654 + provider: github + +tom: + name: tom + email: tom@hotmail.com + photo: more_photos_of_tom + uid: 487984849 + provider: github + + +guest: + name: + email: + photo: 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..2e51d68e31 --- /dev/null +++ b/test/models/category_test.rb @@ -0,0 +1,49 @@ +require "test_helper" + +describe Category do + let(:category) { categories(:haunted) } + + it "must be valid" do + expect(category).must_be :valid? + end + + it 'has required fields' do + field = :name + + expect(category).must_respond_to field + end + + describe 'Validations' do + it 'must have a category name' do + category = categories(:zombie) + category.name = nil + + valid = category.save + + expect(valid).must_equal false + expect(category.errors.messages).must_include :name + end + it 'must have a unique category name' do + other_category = categories(:zombie) + other_category.name = category.name + + valid = other_category.save + + expect(valid).must_equal false + expect(other_category.errors.messages).must_include :name + end + end + + describe 'Relationships' do + it 'can have many products' do + category.products << Product.first + products = category.products + + #Assert + expect(products.length).must_be :>=, 1 + products.each do |product| + expect(product).must_be_instance_of Product + end + end + end +end diff --git a/test/models/order_test.rb b/test/models/order_test.rb new file mode 100644 index 0000000000..02cab007dc --- /dev/null +++ b/test/models/order_test.rb @@ -0,0 +1,107 @@ +require "test_helper" +describe Order do + let(:order) { orders(:one) } + it "must be valid" do + order = Order.new + order.status = "pending" + order.valid?.must_equal true + end + + it "must have required fields" do + fields = [ :status,:street,:city,:state,:zip,:creditcard,:cvv,:billingzip, :name, :email] + + fields.each do |field| + expect(order).must_respond_to field + end + end + + describe 'Relationships' do + it 'can belong to user' do + user = users(:user1) + order.user_id = user.id + + expect(user).must_be_instance_of User + expect(user.id).must_equal order.user.id + end + + it 'can not belong to user' do + order.user_id = nil + + expect(order.user).must_be_nil + end + + it 'can have many products' do + products = order.products + + expect(products.length).must_be :>=, 1 + products.each do |product| + expect(product).must_be_instance_of Product + end + end + + it 'can add new products through order_item' do + num_p = order.products.length + + new_product = OrdersItem.new(order_id: order.id, product_id: products(:product1), quantity: 2) + expect(products.length).must_equal num_p + 1 + order.products.each do |product| + expect(product).must_be_instance_of Product + end + end + end + + describe 'Validations' do + + it 'will not save an invalid status' do + invalid_statuses = ["s", :price, "134e", nil, -1] + order = orders(:one) + + invalid_statuses.each do |invalid_status| + order.status = invalid_status + valid = order.save + expect(valid).must_equal false + expect(order.errors.messages).must_include :status + end + + valid_statuses = ["pending", "paid","complete", "cancelled"] + end + + it 'will save with a valid status' do + valid_statuses = ["pending", "paid","complete", "cancelled"] + order = orders(:one) + + valid_statuses.each do |valid_status| + order.status = valid_status + valid = order.save + expect(valid).must_equal true + end + end + + end + + describe "add_product" do + let(:order) { orders(:one) } + + it "adds product to order" do + product = products(:product1) + + num_p = order.products.count + order.add_product(product.id,1) + + expect(order.products.count).must_equal num_p + 1 + expect(order.products).must_include product + end + + it "increases the quantity of a product is in stock" do + end + + it "will not add item if product is nil" do + + end + + it 'Will sum up an Orders total price' do + #Order 1 has 3 x Product 3 ($10.00) and 1 x Product 2 ($11) + expect(order.order_calculate_total).must_equal 10.00*3 + 11 + end + end +end diff --git a/test/models/ordersitem_test.rb b/test/models/ordersitem_test.rb new file mode 100644 index 0000000000..fe9d845586 --- /dev/null +++ b/test/models/ordersitem_test.rb @@ -0,0 +1,50 @@ +require "test_helper" +describe OrdersItem do + let(:item) { orders_items(:item1) } + it "must be valid" do + item.valid?.must_equal true + end + + it "must have required fields" do + fields = [ :order_id, :product_id, :quantity] + + fields.each do |field| + expect(item).must_respond_to field + end + end + + describe 'Relationships' do + it 'belongs to order' do + order = item.order + + expect(order).must_be_instance_of Order + expect(order.id).must_equal item.order_id + end + + it 'belongs to product' do + product = item.product + + expect(product).must_be_instance_of Product + expect(product.id).must_equal item.product_id + end + end + + describe 'Validations' do + it 'has valid quantity' do + qtys = ["s", :price, "134e", nil, -1] + + qtys.each do |qty| + item.quantity = qty + valid = item.save + expect(valid).must_equal false + end + end + + end + + describe 'Custom Methods' do + it 'can calculate total revenue for an item' do + expect(item.calculate_total).must_equal 10.00*3 + end + end +end diff --git a/test/models/product_test.rb b/test/models/product_test.rb new file mode 100644 index 0000000000..15a773ce8b --- /dev/null +++ b/test/models/product_test.rb @@ -0,0 +1,186 @@ +require "test_helper" +describe Product do + let(:product) { products(:product1) } + + it "must be valid" do + expect(product).must_be :valid? + end + + it "must have required fields" do + fields = [:name, :price, :description, :photo, :stock] + + fields.each do |field| + expect(product).must_respond_to field + end + end + + describe 'Relationships' do + it 'belongs to user' do + user = product.user + + expect(user).must_be_instance_of User + expect(user.id).must_equal product.user_id + end + + it 'can have many categories' do + product.categories << Category.first + categories = product.categories + + expect(categories.length).must_be :>=, 1 + categories.each do |category| + expect(category).must_be_instance_of Category + end + + end + + it 'can have many orders' do + + orders = product.orders + + expect(orders.length).must_be :>=, 1 + orders.each do |order| + expect(order).must_be_instance_of Order + end + end + end + + describe 'Validations' do + it 'must have name' do + product = products(:product2) + product.name = nil + + valid = product.save + expect(valid).must_equal false + expect(product.errors.messages).must_include :name + end + + it 'must have a unique product name for given merchant' do + product1 = products(:product1) + product3 = products(:product3) + product3.name = product1.name + + valid = product3.save + expect(valid).must_equal false + expect(product3.errors.messages).must_include :name + end + + it 'different merchants can have the same product name' do + product1 = products(:product1) + product2 = products(:product2) + product2.name = product1.name + + valid = product2.save + expect(valid).must_equal true + + end + + it 'must have price' do + product = products(:product2) + product.price = nil + + valid = product.save + expect(valid).must_equal false + expect(product.errors.messages).must_include :price + end + + it 'must have a number greater than 0 for price' do + invalid_prices = ["s", :price, "134e", nil, -1] + product = products(:product1) + + invalid_prices.each do |invalid_price| + product.price = invalid_price + valid = product.save + expect(valid).must_equal false + expect(product.errors.messages).must_include :price + end + end + + it 'must have description' do + product = products(:product2) + product.description = nil + + valid = product.save + expect(valid).must_equal false + expect(product.errors.messages).must_include :description + end + + it 'must have photo' do + product = products(:product2) + product.photo = nil + + valid = product.save + expect(valid).must_equal false + expect(product.errors.messages).must_include :photo + end + + it 'must have a valid stock' do + product = products(:product2) + invalid_stocks = ["s", :price, "134e", nil, -1] + + invalid_stocks.each do |stock| + product.stock = stock + valid = product.save + expect(valid).must_equal false + expect(product.errors.messages).must_include :stock + end + end + + end + + describe 'Custom Methods' do + it 'must create a list of unique categories' do + category_list = Product.category_list + category_list.each do |category| + expect(Category.all).must_include category + end + end + + it 'can reduce stock when there is enough stock' do + stock_check = product.stock + product.reduce_stock(1) + expect(product.stock).must_equal stock_check - 1 + + end + + it 'will not reduce stock if there is not enough' do + product.reduce_stock(5) + valid = product.save + expect(valid).must_equal false + expect(product.errors.messages).must_include :stock + end + + end + + describe 'Can add product to cart?' do + it 'returns true if stock is availble to fill order' do + #arrange + product = products(:product2) + quantity = product.stock - 1 + #act + result = product.can_purchase?(quantity) + #assert + expect(result).must_equal true + end + + it 'returns false if stock is unavailable to fill order' do + #arrange + product = products(:product2) + quantity = product.stock + 1 + #act + result = product.can_purchase?(quantity) + #assert + expect(result).must_equal false + + end + it "returns true if stock is equal to requested quantity" do + #arrange + product = products(:product2) + quantity = product.stock + #act + result = product.can_purchase?(quantity) + #assert + expect(result).must_equal true + end + end + +end diff --git a/test/models/user_test.rb b/test/models/user_test.rb new file mode 100644 index 0000000000..0775e54563 --- /dev/null +++ b/test/models/user_test.rb @@ -0,0 +1,167 @@ +require "test_helper" + +describe User do + let(:user) { users(:dani) } + + it "must be valid" do + expect(user).must_be :valid? + end + + it "must have required fields" do + fields = [:name, :email, :photo, :uid, :provider] + + fields.each do |field| + expect(user).must_respond_to field + end + end + + describe 'Relationships' do + + it 'can have many products' do + user.products << Product.first + products = user.products + + expect(products.length).must_be :>=, 1 + products.each do |product| + expect(product).must_be_instance_of Product + end + + end + + it 'can have many orders' do + user.orders << Order.first + orders = user.orders + + expect(orders.length).must_be :>=, 1 + orders.each do |order| + expect(order).must_be_instance_of Order + end + end + end + describe 'Validations' do + it 'must have name' do + user = users(:dani) + user.name = nil + + valid = user.save + expect(valid).must_equal false + expect(user.errors.messages).must_include :name + + end + + it 'must have valid email' do + invalid_emails = [nil , "@", "dan"] + + user = users(:dani) + + invalid_emails.each do |invalid_email| + user.email = invalid_email + + valid = user.save + expect(valid).must_equal false + expect(user.errors.messages).must_include :email + end + end + + it 'must have a unique email' do + user = users(:dani) + user2 = users(:kay) + user2.email = user.email + + valid = user2.save + expect(valid).must_equal false + expect(user2.errors.messages).must_include :email + end + + end + + describe "find my products" do + before do + # Make a merchant with no products + @merchant = User.create( + name: "Cassy", + email: "casy@g.com", + uid: 234343434, + provider: 'github' + ) + # Logging user in + # OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new(mock_auth_hash(user)) + # get auth_callback_path('github') + + end + it "returns an empty array if I have no products" do + # Merchant has no products + @merchant.sold_items.must_be_instance_of Array + @merchant.sold_items.length.must_equal 0 + + end + + it "returns an empty array if my products have no items" do + # Add a product to a merchant but have no ordersitems + product = Product.create( + name: "Spooky House", + price: 1000000, + description: "Most spooky house ever!", + stock: 3, + user_id: @merchant.id + ) + + @merchant.products << product + @merchant.products.length.must_equal 1 + @merchant.products.first.must_equal product + @merchant.sold_items.must_be_instance_of Array + @merchant.sold_items.length.must_equal 0 + end + + it "returns my order items" do + # Add two products to a merchant, each with orders items + # Product one + # order item + # order item + # product two + # order item + # order item + + product_one = Product.create( + name: "Spooky House One", + price: 1000000, + description: "Most spooky house ever!", + stock: 3, + user_id: @merchant.id, + photo: "product_one_photo" + ) + + product_two = Product.create( + name: "Spooky House Two", + price: 1000000, + description: "Most spooky house ever!", + stock: 3, + user_id: @merchant.id, + photo: "product_two_photo" + ) + + # Do I need to shovel order items into product? + product_one.orders_items << OrdersItem.create( + order_id: orders(:one).id, + quantity: 3 + ) + + product_two.orders_items << OrdersItem.create( + order_id: orders(:one).id, + quantity: 2 + ) + + product_one.orders_items.length.must_equal 1 + product_one.orders_items.first.must_be_instance_of OrdersItem + product_one.orders_items.first.order_id.must_equal orders(:one).id + product_one.orders_items.first.quantity.must_equal 3 + product_two.orders_items.length.must_equal 1 + product_two.orders_items.first.must_be_instance_of OrdersItem + product_two.orders_items.first.order_id.must_equal orders(:one).id + product_two.orders_items.first.quantity.must_equal 2 + + + 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..65c36903c2 --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,57 @@ +require 'simplecov' +SimpleCov.start 'rails' do + add_filter '/bin/' + add_filter '/db/' + add_filter '/spec/' # for rspec + add_filter '/test/' # for minitest +end +ENV["RAILS_ENV"] = "test" + +require File.expand_path("../../config/environment", __FILE__) +require "rails/test_help" +require "minitest/rails" +require "minitest/reporters" # for Colorized output +# For colorful output! +Minitest::Reporters.use!( + Minitest::Reporters::SpecReporter.new, + ENV, + Minitest.backtrace_filter +) + + + +# To add Capybara feature tests add `gem "minitest-rails-capybara"` +# to the test group in the Gemfile and uncomment the following: +# require "minitest/rails/capybara" + +# Uncomment for awesome colorful output +require "minitest/pride" + +class ActiveSupport::TestCase + # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. + fixtures :all + def setup + # Once you have enabled test mode, all requests + # to OmniAuth will be short circuited to use the mock authentication hash. + # A request to /auth/provider will redirect immediately to /auth/provider/callback. + OmniAuth.config.test_mode = true + end + + def perform_login(user) + OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new(mock_auth_hash(user)) + get auth_callback_path(:github) + end + + # Turn an instance of class user into + # a fake auth hash + def mock_auth_hash(user) + return { + provider: user.provider, + uid: user.uid, + info: { + email: user.email, + name: user.name + } + } + end +end diff --git a/tmp/.keep b/tmp/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vendor/.keep b/vendor/.keep new file mode 100644 index 0000000000..e69de29bb2