7 | Site:
8 | Twitter:
9 | Location:
10 |
11 | /* THANKS */
12 | Daniel Kehoe (@rails_apps) for the RailsApps project
13 |
14 | /* SITE */
15 | Standards: HTML5, CSS3
16 | Components: jQuery
17 | Software: Ruby on Rails
18 |
19 | /* GENERATED BY */
20 | Rails Composer: http://railscomposer.com/
21 |
--------------------------------------------------------------------------------
/app/controllers/api/v1/api_controller.rb:
--------------------------------------------------------------------------------
1 | module Api
2 | module V1
3 | class ApiController < ApplicationController
4 | before_filter :authenticate_user!
5 | before_filter :validate_params
6 |
7 | def render_json(model, success)
8 | if success
9 | render json: model
10 | else
11 | render json: { errors: model.errors }, status: 422
12 | end
13 | end
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/app/models/user.rb:
--------------------------------------------------------------------------------
1 | class User < ActiveRecord::Base
2 | devise :database_authenticatable, :registerable,
3 | :recoverable, :rememberable, :trackable, :validatable
4 |
5 | has_many :line_items
6 | has_many :items, :through => :line_items
7 |
8 | def self.authenticate(guid)
9 | User.find_or_create_by(guid: guid) do |user|
10 | user.email = guid
11 | end
12 | end
13 |
14 | def admin?
15 | admin
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/app/views/layouts/_messages.html.erb:
--------------------------------------------------------------------------------
1 | <%# Rails flash messages styled for Bootstrap 3.0 %>
2 | <% flash.each do |name, msg| %>
3 | <% if msg.is_a?(String) %>
4 |
5 | ×
6 | <%= content_tag :div, msg, :id => "flash_#{name}" %>
7 |
8 | <% end %>
9 | <% end %>
10 |
--------------------------------------------------------------------------------
/bin/htmldiff:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | #
3 | # This file was generated by Bundler.
4 | #
5 | # The application 'htmldiff' is installed as part of a gem, and
6 | # this file is here to facilitate running it.
7 | #
8 |
9 | require 'pathname'
10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11 | Pathname.new(__FILE__).realpath)
12 |
13 | require 'rubygems'
14 | require 'bundler/setup'
15 |
16 | load Gem.bin_path('diff-lcs', 'htmldiff')
17 |
--------------------------------------------------------------------------------
/bin/httparty:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | #
3 | # This file was generated by Bundler.
4 | #
5 | # The application 'httparty' is installed as part of a gem, and
6 | # this file is here to facilitate running it.
7 | #
8 |
9 | require 'pathname'
10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11 | Pathname.new(__FILE__).realpath)
12 |
13 | require 'rubygems'
14 | require 'bundler/setup'
15 |
16 | load Gem.bin_path('httparty', 'httparty')
17 |
--------------------------------------------------------------------------------
/bin/nokogiri:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | #
3 | # This file was generated by Bundler.
4 | #
5 | # The application 'nokogiri' is installed as part of a gem, and
6 | # this file is here to facilitate running it.
7 | #
8 |
9 | require 'pathname'
10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11 | Pathname.new(__FILE__).realpath)
12 |
13 | require 'rubygems'
14 | require 'bundler/setup'
15 |
16 | load Gem.bin_path('nokogiri', 'nokogiri')
17 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | #
3 | # This file was generated by Bundler.
4 | #
5 | # The application 'setup' is installed as part of a gem, and
6 | # this file is here to facilitate running it.
7 | #
8 |
9 | require 'pathname'
10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11 | Pathname.new(__FILE__).realpath)
12 |
13 | require 'rubygems'
14 | require 'bundler/setup'
15 |
16 | load Gem.bin_path('factory_girl_rails', 'setup')
17 |
--------------------------------------------------------------------------------
/bin/sprockets:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | #
3 | # This file was generated by Bundler.
4 | #
5 | # The application 'sprockets' is installed as part of a gem, and
6 | # this file is here to facilitate running it.
7 | #
8 |
9 | require 'pathname'
10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11 | Pathname.new(__FILE__).realpath)
12 |
13 | require 'rubygems'
14 | require 'bundler/setup'
15 |
16 | load Gem.bin_path('sprockets', 'sprockets')
17 |
--------------------------------------------------------------------------------
/app/models/share_queue.rb:
--------------------------------------------------------------------------------
1 | class ShareQueue < ActiveRecord::Base
2 | has_many :line_items
3 |
4 | validates :recipient_id, presence: true
5 | validates :creator_id, presence: true
6 |
7 | scope :created_by, lambda { |user|
8 | where(creator_id: user.id)
9 | }
10 |
11 | scope :received_by, lambda { |user|
12 | where(recipient_id: user.id)
13 | }
14 |
15 | def complete?
16 | line_items.where(liked: nil).empty?
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/bin/sass-convert:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | #
3 | # This file was generated by Bundler.
4 | #
5 | # The application 'sass-convert' is installed as part of a gem, and
6 | # this file is here to facilitate running it.
7 | #
8 |
9 | require 'pathname'
10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11 | Pathname.new(__FILE__).realpath)
12 |
13 | require 'rubygems'
14 | require 'bundler/setup'
15 |
16 | load Gem.bin_path('sass', 'sass-convert')
17 |
--------------------------------------------------------------------------------
/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 |
--------------------------------------------------------------------------------
/spec/features/visitors/home_page_spec.rb:
--------------------------------------------------------------------------------
1 | # Feature: Home page
2 | # As a visitor
3 | # I want to visit a home page
4 | # So I can learn more about the website
5 | feature 'Home page' do
6 |
7 | # Scenario: Visit the home page
8 | # Given I am a visitor
9 | # When I visit the home page
10 | # Then I see "Welcome"
11 | scenario 'visit the home page' do
12 | visit root_path
13 | expect(page).to have_content 'Home'
14 | end
15 |
16 | end
17 |
--------------------------------------------------------------------------------
/bin/kill-pry-rescue:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | #
3 | # This file was generated by Bundler.
4 | #
5 | # The application 'kill-pry-rescue' is installed as part of a gem, and
6 | # this file is here to facilitate running it.
7 | #
8 |
9 | require 'pathname'
10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11 | Pathname.new(__FILE__).realpath)
12 |
13 | require 'rubygems'
14 | require 'bundler/setup'
15 |
16 | load Gem.bin_path('pry-rescue', 'kill-pry-rescue')
17 |
--------------------------------------------------------------------------------
/app/assets/javascripts/amazon_items.coffee:
--------------------------------------------------------------------------------
1 | $ ->
2 | $('.search-button').click ->
3 | options = {}
4 | if $('.option1-key').val()
5 | options[$('.option1-key').val()] = $('.option1-value').val()
6 |
7 | data =
8 | type: $('.search-type:selected').text()
9 | term: $('.search-term').val()
10 | searchOptions: options
11 |
12 | $.post('/admin/amazon_items/search', data)
13 | .done (res) ->
14 | console.log res
15 |
--------------------------------------------------------------------------------
/config/initializers/devise_permitted_parameters.rb:
--------------------------------------------------------------------------------
1 | module DevisePermittedParameters
2 | extend ActiveSupport::Concern
3 |
4 | included do
5 | before_filter :configure_permitted_parameters
6 | end
7 |
8 | protected
9 |
10 | def configure_permitted_parameters
11 | devise_parameter_sanitizer.for(:sign_up) << :name
12 | devise_parameter_sanitizer.for(:account_update) << :name
13 | end
14 |
15 | end
16 |
17 | DeviseController.send :include, DevisePermittedParameters
18 |
--------------------------------------------------------------------------------
/app/controllers/admin/admin_controller.rb:
--------------------------------------------------------------------------------
1 | module Admin
2 | class AdminController < ApplicationController
3 | before_filter :require_admin
4 |
5 | def index
6 | @delayed_jobs = Delayed::Job.all
7 | end
8 |
9 | def fetch
10 | fetcher = Services::Popshops::ItemFetcher.new(page: 1, category: params[:category])
11 | count = fetcher.do_request
12 |
13 | flash[:notice] = "Added #{count} additional items"
14 |
15 | redirect_to '/admin'
16 | end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | protect_from_forgery with: :null_session
3 | before_action :authenticate_user!
4 | before_action :parse_params
5 |
6 | def require_admin
7 | unless current_user.admin?
8 | redirect_to 'users/sign_in'
9 | end
10 | end
11 |
12 | def parse_params
13 | params.each do |k, v|
14 | params[k] = true if v == 'true'
15 | params[k] = false if v == 'false'
16 | end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/spec/support/database_cleaner.rb:
--------------------------------------------------------------------------------
1 | RSpec.configure do |config|
2 | config.before(:suite) do
3 | DatabaseCleaner.clean_with(:truncation)
4 | end
5 |
6 | config.before(:each) do
7 | DatabaseCleaner.strategy = :transaction
8 | end
9 |
10 | config.before(:each, :js => true) do
11 | DatabaseCleaner.strategy = :truncation
12 | end
13 |
14 | config.before(:each) do
15 | DatabaseCleaner.start
16 | end
17 |
18 | config.append_after(:each) do
19 | DatabaseCleaner.clean
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/app/models/line_item.rb:
--------------------------------------------------------------------------------
1 | class LineItem < ActiveRecord::Base
2 | belongs_to :user
3 | belongs_to :item
4 | belongs_to :share_queue
5 |
6 | validates_presence_of :user_id, :item_id
7 | validates_inclusion_of :liked, in: [true, false, nil]
8 | validates :user_id, uniqueness: { :scope => :item_id }
9 |
10 | def can_destroy?(user)
11 | unless user.id == self.user.id
12 | errors.add(:base, "You must own a line_item to destroy it")
13 | return false
14 | end
15 | return true
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/app/views/layouts/_navigation_links.html.erb:
--------------------------------------------------------------------------------
1 | <%# add navigation links to this file %>
2 | <% if user_signed_in? %>
3 | <%= link_to 'Edit account', edit_user_registration_path %>
4 | <%= link_to 'Sign out', destroy_user_session_path, :method=>'delete' %>
5 | <% else %>
6 | <%= link_to 'Sign in', new_user_session_path %>
7 | <%= link_to 'Sign up', new_user_registration_path %>
8 | <% end %>
9 | <% if user_signed_in? %>
10 | <%= link_to 'Users', users_path %>
11 | <% end %>
12 |
--------------------------------------------------------------------------------
/config/database.yml:
--------------------------------------------------------------------------------
1 | development:
2 | adapter: mysql2
3 | encoding: utf8
4 | database: amzn
5 | username: root
6 | password: ''
7 | host: 127.0.0.1
8 | port: 3306
9 | test:
10 | adapter: mysql2
11 | encoding: utf8
12 | database: amzn
13 | username: root
14 | password: ''
15 | host: 127.0.0.1
16 | port: 3306
17 | production:
18 | adapter: mysql2
19 | encoding: utf8
20 | database: amzn
21 | username: root
22 | password: <%= ENV['APP_USER_MYSQL_PASSWORD'] %>
23 | host: localhost
24 | port: 3306
25 |
--------------------------------------------------------------------------------
/config/initializers/assets.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Version of your assets, change this if you want to expire all your assets.
4 | Rails.application.config.assets.version = '1.0'
5 |
6 | # Add additional assets to the asset load path
7 | # Rails.application.config.assets.paths << Emoji.images_path
8 |
9 | # Precompile additional assets.
10 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
11 | # Rails.application.config.assets.precompile += %w( search.js )
12 |
--------------------------------------------------------------------------------
/spec/controllers/line_items_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe LineItemsController, :type => :controller do
4 | before do
5 | @line_item = create(:line_item)
6 | @user = create(:user)
7 | sign_in @user
8 | end
9 |
10 | describe '#index' do
11 | it 'returns line_items' do
12 | get :index
13 | expect(assigns(:line_items)).to eq([@line_item])
14 | end
15 |
16 | it 'renders the index template' do
17 | get :index
18 | expect(response).to render_template("index")
19 | end
20 | end
21 | end
--------------------------------------------------------------------------------
/app/views/devise/passwords/new.html.erb:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/app/services/fetch_manager.rb:
--------------------------------------------------------------------------------
1 | module Services
2 | class FetchManager
3 | def fetch
4 | SearchNode.by_vendor('Popshops').find_each do |node|
5 | opts = { page: last_page_fetched(node), category: node.vendor_id }
6 | Services::Popshops::ItemFetcher.new(opts).do_request
7 | end
8 | end
9 |
10 | private
11 |
12 | def last_page_fetched(node)
13 | log = Log.searches_for_node(node).first
14 |
15 | if log && log.page_fetched < log.total_pages
16 | 1 + log.page_fetched
17 | else
18 | 1
19 | end
20 | end
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # This file contains settings for ActionController::ParamsWrapper which
4 | # is enabled by default.
5 |
6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7 | ActiveSupport.on_load(:action_controller) do
8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters)
9 | end
10 |
11 | # To enable root element in JSON for ActiveRecord objects.
12 | # ActiveSupport.on_load(:active_record) do
13 | # self.include_root_in_json = true
14 | # end
15 |
--------------------------------------------------------------------------------
/spec/support/helpers/session_helpers.rb:
--------------------------------------------------------------------------------
1 | module Features
2 | module SessionHelpers
3 | def sign_up_with(email, password, confirmation)
4 | visit new_user_registration_path
5 | fill_in 'Email', with: email
6 | fill_in 'Password', with: password
7 | fill_in 'Password confirmation', :with => confirmation
8 | click_button 'Sign up'
9 | end
10 |
11 | def signin(email, password)
12 | visit new_user_session_path
13 | fill_in 'Email', with: email
14 | fill_in 'Password', with: password
15 | click_button 'Sign in'
16 | end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/spec/features/visitors/navigation_spec.rb:
--------------------------------------------------------------------------------
1 | # Feature: Navigation links
2 | # As a visitor
3 | # I want to see navigation links
4 | # So I can find home, sign in, or sign up
5 | feature 'Navigation links', :devise do
6 |
7 | # Scenario: View navigation links
8 | # Given I am a visitor
9 | # When I visit the home page
10 | # Then I see "home," "sign in," and "sign up"
11 | scenario 'view navigation links' do
12 | visit root_path
13 | expect(page).to have_content 'Home'
14 | expect(page).to have_content 'Sign in'
15 | expect(page).to have_content 'Sign up'
16 | end
17 |
18 | end
19 |
--------------------------------------------------------------------------------
/app/views/admin/admin/index.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
Manually fetch a category
3 | <%= form_tag("/admin/fetch", method: "post") do %>
4 | <%= label_tag(:category, "Fetch by category:") %>
5 | <%= text_field_tag(:category) %>
6 | <%= submit_tag("Search") %>
7 | <% end %>
8 |
9 |
10 |
11 |
12 |
13 |
Active Delayed Jobs
14 |
15 | <% @delayed_jobs.each do |job| %>
16 | <%= job %>
17 | <% end %>
18 | <% if @delayed_jobs.count == 0 %>
19 | There are no delayed jobs.
20 | <% end %>
21 |
22 |
23 |
--------------------------------------------------------------------------------
/spec/controllers/application_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Api::V1::ItemsController, :type => :controller do
4 | describe '#parse_params' do
5 | it 'coerces string true and false to boolean true/fase' do
6 | subject.params = {
7 | truthy: 'true',
8 | falsey: 'false'
9 | }
10 |
11 | expect(subject.params["truthy"]).not_to be true
12 | expect(subject.params["falsey"]).not_to be false
13 |
14 | subject.parse_params
15 |
16 | expect(subject.params["truthy"]).to be true
17 | expect(subject.params["falsey"]).to be false
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 | root to: 'visitors#index'
3 | devise_for :users
4 | resources :users
5 | resources :items, :only => [:index, :show]
6 | resources :line_items, :only => [:index, :create, :destroy]
7 |
8 | scope 'api/v1' do
9 | resources :line_items, :only => [:index, :create, :destroy, :update], controller: 'api/v1/line_items'
10 | resources :items, :only => [:index], controller: 'api/v1/items'
11 | resources :share_queues, :only => [:index, :create], controller: 'api/v1/share_queues'
12 | end
13 |
14 | scope 'admin' do
15 | get '/', :to => 'admin/admin#index'
16 | post '/fetch', :to => 'admin/admin#fetch'
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/spec/features/users/user_index_spec.rb:
--------------------------------------------------------------------------------
1 | include Warden::Test::Helpers
2 | Warden.test_mode!
3 |
4 | # Feature: User index page
5 | # As a user
6 | # I want to see a list of users
7 | # So I can see who has registered
8 | feature 'User index page', :devise do
9 |
10 | after(:each) do
11 | Warden.test_reset!
12 | end
13 |
14 | # Scenario: User listed on index page
15 | # Given I am signed in
16 | # When I visit the user index page
17 | # Then I see my own email address
18 | scenario 'user sees own email address' do
19 | user = FactoryGirl.create(:user)
20 | login_as(user, scope: :user)
21 | visit users_path
22 | expect(page).to have_content user.email
23 | end
24 |
25 | end
26 |
--------------------------------------------------------------------------------
/app/views/line_items/index.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Items
4 |
5 |
6 | Name
7 | Price
8 | Thumbnail
9 | Item Detail
10 | Liked By
11 |
12 |
13 | <% if @line_items.count > 0 %>
14 | <% @line_items.each do |line_item| %>
15 |
16 | <%= render line_item %>
17 |
18 | <% end %>
19 | <% else %>
20 | Nobody has liked anything.
21 | <% end %>
22 |
23 |
24 | <%= will_paginate @line_items %>
25 |
26 |
27 |
--------------------------------------------------------------------------------
/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, '\1en'
8 | # inflect.singular /^(ox)en/i, '\1'
9 | # inflect.irregular 'person', 'people'
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym 'RESTful'
16 | # end
17 |
--------------------------------------------------------------------------------
/app/controllers/api/v1/items_controller.rb:
--------------------------------------------------------------------------------
1 | module Api
2 | module V1
3 | class ItemsController < Api::V1::ApiController
4 | def index
5 | items = Item.where(nil)
6 | items = items.liked_by(current_user) if params[:liked]
7 | items = items.disliked_by(current_user) if params[:disliked]
8 | items = items.unrated_by(current_user) if params[:unrated]
9 |
10 | render json: items.paginate(page: params[:page]).as_json
11 | end
12 |
13 | private
14 |
15 | def validate_params
16 | param! :page, Integer, default: 1
17 | param! :liked, :boolean
18 | param! :disliked, :boolean
19 | param! :unrated, :boolean
20 | end
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization
2 | # and are automatically loaded by Rails. If you want to use locales other
3 | # than English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t 'hello'
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t('hello') %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # To learn more, please read the Rails Internationalization guide
20 | # available at http://guides.rubyonrails.org/i18n.html.
21 |
22 | en:
23 | hello: "Hello world"
24 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/application.css.scss:
--------------------------------------------------------------------------------
1 | /*
2 | * This is a manifest file that'll be compiled into application.css, which will include all the files
3 | * listed below.
4 | *
5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7 | *
8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9 | * compiled file so the styles you add here take precedence over styles defined in any styles
10 | * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
11 | * file per style scope.
12 | *
13 | *= require_tree .
14 | *= require_self
15 | */
16 |
--------------------------------------------------------------------------------
/app/views/devise/passwords/edit.html.erb:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/app/views/layouts/_navigation.html.erb:
--------------------------------------------------------------------------------
1 | <%# navigation styled for Bootstrap 3.0 %>
2 |
3 |
4 |
13 |
14 |
15 | <%= render 'layouts/navigation_links' %>
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/assets/javascripts/application.js:
--------------------------------------------------------------------------------
1 | // This is a manifest file that'll be compiled into application.js, which will include all the files
2 | // listed below.
3 | //
4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5 | // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6 | //
7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8 | // compiled file.
9 | //
10 | // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
11 | // about supported directives.
12 | //
13 | //= require jquery
14 | //= require jquery_ujs
15 | //= require turbolinks
16 | //= require bootstrap-sprockets
17 | //= require application
18 | //= require_tree .
19 |
20 | Amzn = {};
--------------------------------------------------------------------------------
/app/assets/javascripts/items.coffee:
--------------------------------------------------------------------------------
1 | class Amzn.ItemsView
2 | constructor: ->
3 | $('.like').click @onLikeClick
4 | $('.unlike').click @onUnlikeClick
5 |
6 | onLikeClick: (e) =>
7 | $item = $(e.target)
8 | id = $item.closest('tr').data('id')
9 | $.post('/api/v1/line_items', { item_id: id })
10 | .done( => @onLikeDone($item))
11 |
12 | onLikeDone: ($item) =>
13 | $item.parent().find('.unlike').removeClass('hidden')
14 | $item.addClass('hidden')
15 |
16 | onUnlikeClick: (e) =>
17 | $item = $(e.target)
18 | id = $item.closest('tr').data('id')
19 | $.ajax
20 | type: 'DELETE'
21 | url: "api/v1/line_items/#{id}"
22 | success: => @onUnlikeDone($item)
23 |
24 | onUnlikeDone: ($item) =>
25 | $item.parent().find('.like').removeClass('hidden')
26 | $item.addClass('hidden')
27 |
28 |
--------------------------------------------------------------------------------
/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | <%= content_for?(:title) ? yield(:title) : "Amzn" %>
6 | ">
7 | <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
8 | <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
9 | <%= csrf_meta_tags %>
10 |
11 |
12 |
13 | <%= render 'layouts/navigation' %>
14 |
15 |
16 | <%= render 'layouts/messages' %>
17 |
18 | <%= yield %>
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/config/nginx.conf:
--------------------------------------------------------------------------------
1 | upstream puma {
2 | server unix:///home/root/apps/amzn/shared/tmp/sockets/amzn-puma.sock;
3 | }
4 |
5 | server {
6 | listen 80 default_server deferred;
7 | # server_name example.com;
8 |
9 | root /home/root/apps/amzn/current/public;
10 | access_log /home/root/apps/amzn/current/log/nginx.access.log;
11 | error_log /home/root/apps/amzn/current/log/nginx.error.log info;
12 |
13 | location ^~ /assets/ {
14 | gzip_static on;
15 | expires max;
16 | add_header Cache-Control public;
17 | }
18 |
19 | try_files $uri/index.html $uri @puma;
20 | location @puma {
21 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
22 | proxy_set_header Host $http_host;
23 | proxy_redirect off;
24 |
25 | proxy_pass http://puma;
26 | }
27 |
28 | error_page 500 502 503 504 /500.html;
29 | client_max_body_size 10M;
30 | keepalive_timeout 10;
31 | }
32 |
--------------------------------------------------------------------------------
/spec/features/users/user_delete_spec.rb:
--------------------------------------------------------------------------------
1 | include Warden::Test::Helpers
2 | Warden.test_mode!
3 |
4 | # Feature: User delete
5 | # As a user
6 | # I want to delete my user profile
7 | # So I can close my account
8 | feature 'User delete', :devise, :js do
9 |
10 | after(:each) do
11 | Warden.test_reset!
12 | end
13 |
14 | # Scenario: User can delete own account
15 | # Given I am signed in
16 | # When I delete my account
17 | # Then I should see an account deleted message
18 | scenario 'user can delete own account' do
19 | skip 'skip a slow test'
20 | user = FactoryGirl.create(:user)
21 | login_as(user, :scope => :user)
22 | visit edit_user_registration_path(user)
23 | click_button 'Cancel my account'
24 | page.driver.browser.switch_to.alert.accept
25 | expect(page).to have_content I18n.t 'devise.registrations.destroyed'
26 | end
27 |
28 | end
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/app/views/devise/registrations/new.html.erb:
--------------------------------------------------------------------------------
1 |
24 |
--------------------------------------------------------------------------------
/app/views/amazon_items/index.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
Select a search type
3 |
4 | ItemSearch
5 |
6 |
7 |
8 |
9 |
Enter a search term
10 |
11 |
12 |
13 |
36 |
37 |
38 | Search
39 |
--------------------------------------------------------------------------------
/app/views/admin/amazon_items/index.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
Select a search type
3 |
4 | ItemSearch
5 |
6 |
7 |
8 |
9 |
Enter a search term
10 |
11 |
12 |
13 |
36 |
37 |
38 | Search
39 |
--------------------------------------------------------------------------------
/spec/models/line_item_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe LineItem, :type => :model do
4 |
5 | before do
6 | @line_item = create(:line_item)
7 | end
8 |
9 | describe 'builder' do
10 | it 'is valid' do
11 | expect(@line_item).to be_valid
12 | end
13 | end
14 |
15 | describe 'validations' do
16 | it 'user_id and item_id pair must be unique' do
17 | line_item2 = create(:line_item)
18 | line_item2.user = @line_item.user
19 | line_item2.item = @line_item.item
20 |
21 | expect(line_item2).not_to be_valid
22 | end
23 |
24 | it 'liked can be null' do
25 | @line_item.liked = nil
26 |
27 | expect(@line_item).to be_valid
28 | end
29 | end
30 |
31 | describe '#can_destroy' do
32 | it 'line_items user can destroy the line item' do
33 | expect(@line_item.can_destroy?(@line_item.user)).to be true
34 | end
35 |
36 | it 'other user cannot destroy the line_item' do
37 | expect(@line_item.can_destroy?(build(:user))).to be false
38 | end
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/app/views/items/index.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Items
4 |
5 |
6 |
7 | Name
8 | Price
9 | Thumbnail
10 | Item Detail
11 | Like This!
12 |
13 | <% @items.each do |item| %>
14 | >
15 | <%= render item %>
16 | View Item
17 |
18 |
19 | Like
20 |
21 |
22 | Unlike
23 |
24 |
25 |
26 | <% end %>
27 |
28 |
29 | <%= will_paginate @items %>
30 |
31 |
32 |
33 |
36 |
--------------------------------------------------------------------------------
/app/views/devise/sessions/new.html.erb:
--------------------------------------------------------------------------------
1 |
29 |
--------------------------------------------------------------------------------
/app/controllers/api/v1/line_items_controller.rb:
--------------------------------------------------------------------------------
1 | module Api
2 | module V1
3 | class LineItemsController < Api::V1::ApiController
4 | def index
5 | @line_items = LineItem.paginate(page: params[:page])
6 | render json: @line_items
7 | end
8 |
9 | def create
10 | attrs = { liked: params[:liked], item_id: params[:item_id] }
11 | line_item = current_user.line_items.build(attrs)
12 |
13 | render_json(line_item, line_item.save && current_user.id == line_item.user_id)
14 | end
15 |
16 | def update
17 | line_item = LineItem.find_by_id(params[:id])
18 |
19 | render_json(line_item, line_item.update_attributes(liked: params[:liked]))
20 | end
21 |
22 | def destroy
23 | line_item = LineItem.find_by_id(params[:id])
24 | if line_item.can_destroy?(current_user)
25 | line_item.destroy
26 | end
27 |
28 | render json: line_item
29 | end
30 |
31 | private
32 |
33 | def validate_params
34 | param! :id, Integer
35 | param! :item_id, Integer
36 | param! :liked, :boolean
37 | param! :page, Integer, default: 1
38 | end
39 | end
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/spec/services/fetch_manager_spec.rb:
--------------------------------------------------------------------------------
1 | describe Services::FetchManager do
2 | before do
3 | @search_node = SearchNode.create(vendor: 'Popshops', vendor_id: 1)
4 | end
5 |
6 | context 'when this node has never been fetched' do
7 | it 'fetches with options' do
8 | expect_any_instance_of(Services::Popshops::ItemFetcher).to receive(:initialize).with(page: 1, category: 1)
9 | Services::FetchManager.new.fetch
10 | end
11 | end
12 |
13 | context '#last_page_fetched' do
14 | context 'search node still has pages left' do
15 | before do
16 | Log.create(search_node_id: @search_node.id, page_fetched: 1, total_pages: 4)
17 | end
18 |
19 | it 'fetches with options' do
20 | page = Services::FetchManager.new.send(:last_page_fetched, @search_node)
21 | expect(page).to eq 2
22 | end
23 | end
24 |
25 | context 'search node has no pages left' do
26 | before do
27 | Log.create(search_node_id: @search_node.id, page_fetched: 4, total_pages: 4)
28 | end
29 |
30 | it 'fetches with options' do
31 | page = Services::FetchManager.new.send(:last_page_fetched, @search_node)
32 | expect(page).to eq 1
33 | end
34 | end
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/app/models/item.rb:
--------------------------------------------------------------------------------
1 | class Item < ActiveRecord::Base
2 | self.per_page = 20
3 |
4 | has_many :line_items
5 | has_many :users, :through => :line_items
6 | belongs_to :search_node
7 |
8 | validates :title, presence: true
9 | validates :image_url, presence: true
10 | validates :url, presence: true
11 | validates :price, presence: true
12 |
13 | scope :unrated_by, lambda { |user|
14 | where("not exists (select id from line_items where line_items.item_id = items.id AND line_items.user_id = ?)", user.id)
15 | }
16 |
17 | scope :liked_by, lambda { |user|
18 | joins("left outer join line_items as i on items.id = i.item_id")
19 | .where("i.liked = ?", true)
20 | .where("i.user_id = ?", user.id)
21 | }
22 |
23 | scope :disliked_by, lambda { |user|
24 | joins("left outer join line_items as i on items.id = i.item_id")
25 | .where("i.liked = false")
26 | .where("i.user_id = ?", user.id)
27 | }
28 |
29 | scope :category, lambda { |category_id|
30 | where(category: category_id)
31 | }
32 |
33 | def is_liked?(user)
34 | user.line_items.where(item_id: self.id, liked: true).exists?
35 | end
36 |
37 | def category
38 | search_node.category
39 | end
40 |
41 | def parent_category
42 | search_node.parent_category
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/spec/features/users/user_show_spec.rb:
--------------------------------------------------------------------------------
1 | include Warden::Test::Helpers
2 | Warden.test_mode!
3 |
4 | # Feature: User profile page
5 | # As a user
6 | # I want to visit my user profile page
7 | # So I can see my personal account data
8 | feature 'User profile page', :devise do
9 |
10 | after(:each) do
11 | Warden.test_reset!
12 | end
13 |
14 | # Scenario: User sees own profile
15 | # Given I am signed in
16 | # When I visit the user profile page
17 | # Then I see my own email address
18 | scenario 'user sees own profile' do
19 | user = FactoryGirl.create(:user)
20 | login_as(user, :scope => :user)
21 | visit user_path(user)
22 | expect(page).to have_content 'User'
23 | expect(page).to have_content user.email
24 | end
25 |
26 | # Scenario: User cannot see another user's profile
27 | # Given I am signed in
28 | # When I visit another user's profile
29 | # Then I see an 'access denied' message
30 | scenario "user cannot see another user's profile" do
31 | me = FactoryGirl.create(:user)
32 | other = FactoryGirl.create(:user, email: 'other@example.com')
33 | login_as(me, :scope => :user)
34 | Capybara.current_session.driver.header 'Referer', root_path
35 | visit user_path(other)
36 | expect(page).to have_content 'Access denied.'
37 | end
38 |
39 | end
40 |
--------------------------------------------------------------------------------
/db/migrate/20150109170159_create_delayed_jobs.rb:
--------------------------------------------------------------------------------
1 | class CreateDelayedJobs < ActiveRecord::Migration
2 | def self.up
3 | create_table :delayed_jobs, force: true do |table|
4 | table.integer :priority, default: 0, null: false # Allows some jobs to jump to the front of the queue
5 | table.integer :attempts, default: 0, null: false # Provides for retries, but still fail eventually.
6 | table.text :handler, null: false # YAML-encoded string of the object that will do work
7 | table.text :last_error # reason for last failure (See Note below)
8 | table.datetime :run_at # When to run. Could be Time.zone.now for immediately, or sometime in the future.
9 | table.datetime :locked_at # Set when a client is working on this object
10 | table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead)
11 | table.string :locked_by # Who is working on this object (if locked)
12 | table.string :queue # The name of the queue this job is in
13 | table.timestamps null: true
14 | end
15 |
16 | add_index :delayed_jobs, [:priority, :run_at], name: "delayed_jobs_priority"
17 | end
18 |
19 | def self.down
20 | drop_table :delayed_jobs
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/db/migrate/20150104062211_devise_create_users.rb:
--------------------------------------------------------------------------------
1 | class DeviseCreateUsers < ActiveRecord::Migration
2 | def change
3 | create_table(:users) do |t|
4 | ## Database authenticatable
5 | t.string :email, null: false, default: ""
6 | t.string :encrypted_password, null: false, default: ""
7 |
8 | ## Recoverable
9 | t.string :reset_password_token
10 | t.datetime :reset_password_sent_at
11 |
12 | ## Rememberable
13 | t.datetime :remember_created_at
14 |
15 | ## Trackable
16 | t.integer :sign_in_count, default: 0, null: false
17 | t.datetime :current_sign_in_at
18 | t.datetime :last_sign_in_at
19 | t.string :current_sign_in_ip
20 | t.string :last_sign_in_ip
21 |
22 | ## Confirmable
23 | # t.string :confirmation_token
24 | # t.datetime :confirmed_at
25 | # t.datetime :confirmation_sent_at
26 | # t.string :unconfirmed_email # Only if using reconfirmable
27 |
28 | ## Lockable
29 | # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
30 | # t.string :unlock_token # Only if unlock strategy is :email or :both
31 | # t.datetime :locked_at
32 |
33 |
34 | t.timestamps
35 | end
36 |
37 | add_index :users, :email, unique: true
38 | add_index :users, :reset_password_token, unique: true
39 | # add_index :users, :confirmation_token, unique: true
40 | # add_index :users, :unlock_token, unique: true
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/app/controllers/api/v1/share_queues_controller.rb:
--------------------------------------------------------------------------------
1 | module Api
2 | module V1
3 | class ShareQueuesController < Api::V1::ApiController
4 | def index
5 | share_queues = ShareQueue.paginate(page: params[:page])
6 | share_queues = share_queues.created_by(current_user) if params[:created]
7 | share_queues = share_queues.received_by(current_user) if params[:received]
8 |
9 | render json: share_queues
10 | end
11 |
12 | def create
13 | share_queue = build_queue
14 |
15 | render_json(share_queue, true)
16 | end
17 |
18 | private
19 |
20 | def build_queue
21 | attrs = { creator_id: current_user.id, recipient_id: params[:recipient_id] }
22 | share_queue = ShareQueue.create(attrs)
23 |
24 | ActiveRecord::Base.transaction do
25 | params[:item_ids].each { |item_id| find_or_build_line_item(share_queue.id, item_id) }
26 | end
27 | share_queue
28 | end
29 |
30 | def find_or_build_line_item(share_queue_id, item_id)
31 | line_item = LineItem.find_or_create_by(item_id: item_id, user_id: params[:recipient_id])
32 | line_item.update_attributes(liked: nil, share_queue_id: share_queue_id)
33 | end
34 |
35 | def validate_params
36 | param! :created, :boolean, default: false
37 | param! :received, :boolean, default: false
38 | param! :page, Integer, default: 1
39 | param! :recipient_id, Integer
40 | param! :item_ids, Array
41 | end
42 | end
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/spec/features/users/user_edit_spec.rb:
--------------------------------------------------------------------------------
1 | include Warden::Test::Helpers
2 | Warden.test_mode!
3 |
4 | # Feature: User edit
5 | # As a user
6 | # I want to edit my user profile
7 | # So I can change my email address
8 | feature 'User edit', :devise do
9 |
10 | after(:each) do
11 | Warden.test_reset!
12 | end
13 |
14 | # Scenario: User changes email address
15 | # Given I am signed in
16 | # When I change my email address
17 | # Then I see an account updated message
18 | scenario 'user changes email address' do
19 | user = FactoryGirl.create(:user)
20 | login_as(user, :scope => :user)
21 | visit edit_user_registration_path(user)
22 | fill_in 'Email', :with => 'newemail@example.com'
23 | fill_in 'Current password', :with => user.password
24 | click_button 'Update'
25 | txts = [I18n.t( 'devise.registrations.updated'), I18n.t( 'devise.registrations.update_needs_confirmation')]
26 | expect(page).to have_content(/.*#{txts[0]}.*|.*#{txts[1]}.*/)
27 | end
28 |
29 | # Scenario: User cannot edit another user's profile
30 | # Given I am signed in
31 | # When I try to edit another user's profile
32 | # Then I see my own 'edit profile' page
33 | scenario "user cannot cannot edit another user's profile", :me do
34 | me = FactoryGirl.create(:user)
35 | other = FactoryGirl.create(:user, email: 'other@example.com')
36 | login_as(me, :scope => :user)
37 | visit edit_user_registration_path(other)
38 | expect(page).to have_content 'Edit User'
39 | expect(page).to have_field('Email', with: me.email)
40 | end
41 |
42 | end
43 |
--------------------------------------------------------------------------------
/config/deploy/staging.rb:
--------------------------------------------------------------------------------
1 | set :stage, :staging
2 |
3 | # Simple Role Syntax
4 | # ==================
5 | # Supports bulk-adding hosts to roles, the primary
6 | # server in each group is considered to be the first
7 | # unless any hosts have the primary property set.
8 | role :app, %w{deploy@example.com}
9 | role :web, %w{deploy@example.com}
10 | role :db, %w{deploy@example.com}
11 |
12 | # Extended Server Syntax
13 | # ======================
14 | # This can be used to drop a more detailed server
15 | # definition into the server list. The second argument
16 | # something that quacks like a hash can be used to set
17 | # extended properties on the server.
18 | server 'example.com', user: 'deploy', roles: %w{web app}, my_property: :my_value
19 |
20 | # you can set custom ssh options
21 | # it's possible to pass any option but you need to keep in mind that net/ssh understand limited list of options
22 | # you can see them in [net/ssh documentation](http://net-ssh.github.io/net-ssh/classes/Net/SSH.html#method-c-start)
23 | # set it globally
24 | # set :ssh_options, {
25 | # keys: %w(/home/rlisowski/.ssh/id_rsa),
26 | # forward_agent: false,
27 | # auth_methods: %w(password)
28 | # }
29 | # and/or per server
30 | # server 'example.com',
31 | # user: 'user_name',
32 | # roles: %w{web app},
33 | # ssh_options: {
34 | # user: 'user_name', # overrides user setting above
35 | # keys: %w(/home/user_name/.ssh/id_rsa),
36 | # forward_agent: false,
37 | # auth_methods: %w(publickey password)
38 | # # password: 'please use keys'
39 | # }
40 | # setting per server overrides global ssh_options
41 |
42 | # fetch(:default_env).merge!(rails_env: :staging)
43 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | gem 'rails', '4.2.0'
4 | gem 'mysql2'
5 | gem 'sass-rails', '~> 5.0'
6 | gem 'uglifier', '>= 1.3.0'
7 | gem 'coffee-rails', '~> 4.1.0'
8 | gem 'jquery-rails'
9 | gem 'turbolinks'
10 | gem 'jbuilder', '~> 2.0'
11 | gem 'will_paginate', '~> 3.0.6'
12 | gem 'delayed_job_active_record'
13 | gem 'daemons'
14 | gem 'amazon-ecs'
15 | gem 'rack-cors', :require => 'rack/cors'
16 | gem 'httparty'
17 | gem 'bootstrap-sass'
18 | gem 'devise'
19 | gem 'sendgrid'
20 | gem 'rails_param'
21 | gem 'delayed_job_recurring'
22 | gem 'puma'
23 |
24 | group :development, :test do
25 | gem 'byebug'
26 | gem 'web-console', '~> 2.0'
27 | gem 'spring'
28 | end
29 | group :development do
30 | gem 'pry'
31 | gem 'pry-rescue'
32 | gem 'pry-stack_explorer'
33 | gem 'better_errors'
34 | gem 'binding_of_caller', :platforms=>[:mri_21]
35 | gem 'capistrano', require: false
36 | gem 'capistrano-rvm', require: false
37 | gem 'capistrano-rails', require: false
38 | gem 'capistrano-bundler', require: false
39 | gem 'capistrano3-puma', require: false
40 | gem 'guard-bundler'
41 | gem 'guard-rails'
42 | gem 'guard-rspec'
43 | gem 'hub', :require=>nil
44 | gem 'quiet_assets'
45 | gem 'rails_layout'
46 | gem 'rb-fchange', :require=>false
47 | gem 'rb-fsevent', :require=>false
48 | gem 'rb-inotify', :require=>false
49 | gem 'spring-commands-rspec'
50 | end
51 | group :development, :test do
52 | gem 'factory_girl_rails'
53 | gem 'faker'
54 | gem 'rspec-rails'
55 | gem 'rspec-mocks'
56 | end
57 | group :test do
58 | gem 'capybara'
59 | gem 'database_cleaner'
60 | gem 'launchy'
61 | gem 'selenium-webdriver'
62 | end
63 |
--------------------------------------------------------------------------------
/config/secrets.yml:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Your secret key is used for verifying the integrity of signed cookies.
4 | # If you change this key, all old signed cookies will become invalid!
5 |
6 | # Make sure the secret is at least 30 characters and all random,
7 | # no regular words or you'll be exposed to dictionary attacks.
8 | # You can use `rake secret` to generate a secure secret key.
9 |
10 | # Make sure the secrets in this file are kept private
11 | # if you're sharing your code publicly.
12 |
13 | development:
14 | admin_name: First User
15 | admin_email: user@example.com
16 | admin_password: changeme
17 | email_provider_username: <%= ENV["SENDGRID_USERNAME"] %>
18 | email_provider_password: <%= ENV["SENDGRID_PASSWORD"] %>
19 | domain_name: example.com
20 | secret_key_base: b407886090ffe625a416b56a9d642deef0abe7b728add41c3aac2b1148193b6d27577ba4c8ba13eddcd70668875642f95cead6146080612f5546634ae585fb65
21 |
22 | test:
23 | domain_name: example.com
24 | secret_key_base: 359c3fc1403396c0ef46ed6a98ecc26decf14f4afb9fb7369528d9f6e57d21d5613e2c07751e6032eb9eb5d3b7dddfdf6be910d09f3c0fadb71e504a1894e04f
25 |
26 | # Do not keep production secrets in the repository,
27 | # instead read values from the environment.
28 | production:
29 | admin_name: <%= ENV["ADMIN_NAME"] %>
30 | admin_email: <%= ENV["ADMIN_EMAIL"] %>
31 | admin_password: <%= ENV["ADMIN_PASSWORD"] %>
32 | email_provider_username: <%= ENV["SENDGRID_USERNAME"] %>
33 | email_provider_password: <%= ENV["SENDGRID_PASSWORD"] %>
34 | domain_name: <%= ENV["DOMAIN_NAME"] %>
35 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
36 | secret_token: <%= ENV["SECRET_TOKEN"] %>
37 |
--------------------------------------------------------------------------------
/spec/models/item_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe Item, :type => :model do
4 |
5 | before do
6 | @user = create(:user)
7 |
8 | @disliked_item = create(:item)
9 | LineItem.create(item_id: @disliked_item.id, user_id: @user.id, liked: false)
10 |
11 | @liked_item = create(:item)
12 | LineItem.create(item_id: @liked_item.id, user_id: @user.id, liked: true)
13 |
14 | @unrated_item = create(:item)
15 | end
16 |
17 | describe 'builder' do
18 | it 'is valid' do
19 | expect(@disliked_item).to be_valid
20 | expect(@liked_item).to be_valid
21 | expect(@unrated_item).to be_valid
22 | end
23 | end
24 |
25 | describe 'scopes' do
26 | describe 'unrated_by' do
27 | it 'returns items that the provided user has not rated' do
28 | items = Item.unrated_by(@user)
29 | expect(items).to eq([@unrated_item])
30 | end
31 | end
32 |
33 | describe 'liked_by' do
34 | it 'returns items that the provided user has liked' do
35 | items = Item.liked_by(@user)
36 | expect(items).to eq([@liked_item])
37 | end
38 | end
39 |
40 | describe 'disliked_by' do
41 | it 'returns items that the provided user has liked' do
42 | items = Item.disliked_by(@user)
43 | expect(items).to eq([@disliked_item])
44 | end
45 | end
46 | end
47 |
48 | describe '#is_liked?' do
49 | it 'returns true if a given user likes' do
50 | expect(@liked_item.is_liked?(@user)).to be true
51 | end
52 |
53 | it 'returns false if a given user does not like or is unrated' do
54 | expect(@disliked_item.is_liked?(@user)).to be false
55 | expect(@unrated_item.is_liked?(@user)).to be false
56 | end
57 | end
58 | end
59 |
--------------------------------------------------------------------------------
/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
We're sorry, but something went wrong.
62 |
63 |
If you are the application owner check the logs for more information.
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/framework_and_overrides.css.scss:
--------------------------------------------------------------------------------
1 | // import the CSS framework
2 | @import "bootstrap-sprockets";
3 | @import "bootstrap";
4 |
5 | // make all images responsive by default
6 | img {
7 | @extend .img-responsive;
8 | margin: 0 auto;
9 | }
10 | // override for the 'Home' navigation link
11 | .navbar-brand {
12 | font-size: inherit;
13 | }
14 |
15 | // THESE ARE EXAMPLES YOU CAN MODIFY
16 | // create your own classes
17 | // to make views framework-neutral
18 | .column {
19 | @extend .col-md-6;
20 | @extend .text-center;
21 | }
22 | .form {
23 | @extend .col-md-6;
24 | }
25 | .form-centered {
26 | @extend .col-md-6;
27 | @extend .text-center;
28 | }
29 | .submit {
30 | @extend .btn;
31 | @extend .btn-primary;
32 | @extend .btn-lg;
33 | }
34 | // apply styles to HTML elements
35 | // to make views framework-neutral
36 | main {
37 | @extend .container;
38 | background-color: #eee;
39 | padding-bottom: 80px;
40 | width: 100%;
41 | margin-top: 51px; // accommodate the navbar
42 | }
43 | section {
44 | @extend .row;
45 | margin-top: 20px;
46 | }
47 |
48 | // Styles for form views
49 | // using Bootstrap
50 | // generated by the rails_layout gem
51 | .authform {
52 | padding-top: 30px;
53 | max-width: 320px;
54 | margin: 0 auto;
55 | }
56 | .authform form {
57 | @extend .well;
58 | @extend .well-lg;
59 | padding-bottom: 40px;
60 | }
61 | .authform .right {
62 | float: right !important;
63 | }
64 | .authform .button {
65 | @extend .btn;
66 | @extend .btn-primary;
67 | }
68 | .authform fieldset {
69 | @extend .well;
70 | }
71 | #error_explanation {
72 | @extend .alert;
73 | @extend .alert-danger;
74 | }
75 | #error_explanation h2 {
76 | font-size: 16px;
77 | }
78 | .button-xs {
79 | @extend .btn;
80 | @extend .btn-primary;
81 | @extend .btn-xs;
82 | }
83 |
--------------------------------------------------------------------------------
/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The change you wanted was rejected.
62 |
Maybe you tried to change something you didn't have access to.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The page you were looking for doesn't exist.
62 |
You may have mistyped the address or the page may have moved.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/config/application.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path('../boot', __FILE__)
2 |
3 | require 'rails/all'
4 |
5 | # Require the gems listed in Gemfile, including any gems
6 | # you've limited to :test, :development, or :production.
7 | Bundler.require(*Rails.groups)
8 |
9 | module Amzn
10 | class Application < Rails::Application
11 |
12 | config.generators do |g|
13 | g.test_framework :rspec,
14 | fixtures: true,
15 | view_specs: false,
16 | helper_specs: false,
17 | routing_specs: false,
18 | controller_specs: false,
19 | request_specs: false
20 | g.fixture_replacement :factory_girl, dir: "spec/factories"
21 | end
22 |
23 | config.autoload_paths += %W(#{config.root}/app/)
24 | config.autoload_paths += Dir["#{config.root}/app/**/"]
25 |
26 | config.middleware.insert_before 0, "Rack::Cors" do
27 | allow do
28 | origins '*'
29 | resource '*', :headers => :any, :methods => [:get, :post, :options]
30 | end
31 | end
32 | # Settings in config/environments/* take precedence over those specified here.
33 | # Application configuration should go into files in config/initializers
34 | # -- all .rb files in that directory are automatically loaded.
35 |
36 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
37 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
38 | # config.time_zone = 'Central Time (US & Canada)'
39 |
40 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
41 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
42 | # config.i18n.default_locale = :de
43 |
44 | # Do not swallow errors in after_commit/after_rollback callbacks.
45 | config.active_record.raise_in_transactional_callbacks = true
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | #----------------------------------------------------------------------------
2 | # Ignore these files when commiting to a git repository.
3 | #
4 | # See http://help.github.com/ignore-files/ for more about ignoring files.
5 | #
6 | # The original version of this file is found here:
7 | # https://github.com/RailsApps/rails-composer/blob/master/files/gitignore.txt
8 | #
9 | # Corrections? Improvements? Create a GitHub issue:
10 | # http://github.com/RailsApps/rails-composer/issues
11 | #----------------------------------------------------------------------------
12 |
13 | # bundler state
14 | /.bundle
15 | /vendor/bundle/
16 | /vendor/ruby/
17 |
18 | # minimal Rails specific artifacts
19 | db/*.sqlite3
20 | /db/*.sqlite3-journal
21 | /log/*
22 | /tmp/*
23 |
24 | # various artifacts
25 | **.war
26 | *.rbc
27 | *.sassc
28 | .redcar/
29 | .sass-cache
30 | /config/config.yml
31 | /coverage.data
32 | /coverage/
33 | /db/*.javadb/
34 | /db/*.sqlite3
35 | /doc/api/
36 | /doc/app/
37 | /doc/features.html
38 | /doc/specs.html
39 | /public/cache
40 | /public/stylesheets/compiled
41 | /public/system/*
42 | /spec/tmp/*
43 | /cache
44 | /capybara*
45 | /capybara-*.html
46 | /gems
47 | /specifications
48 | rerun.txt
49 | pickle-email-*.html
50 | .zeus.sock
51 |
52 | # If you find yourself ignoring temporary files generated by your text editor
53 | # or operating system, you probably want to add a global ignore instead:
54 | # git config --global core.excludesfile ~/.gitignore_global
55 | #
56 | # Here are some files you may want to ignore globally:
57 |
58 | # scm revert files
59 | **.orig
60 |
61 | # Mac finder artifacts
62 | .DS_Store
63 |
64 | # Netbeans project directory
65 | /nbproject/
66 |
67 | # RubyMine project files
68 | .idea
69 |
70 | # Textmate project files
71 | /*.tmproj
72 |
73 | # vim artifacts
74 | **.swp
75 |
76 | # Environment files that may contain sensitive data
77 | .env
78 | .powenv
79 |
80 | api_keys.rb
81 |
--------------------------------------------------------------------------------
/app/views/devise/registrations/edit.html.erb:
--------------------------------------------------------------------------------
1 |
37 |
42 |
--------------------------------------------------------------------------------
/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # The test environment is used exclusively to run your application's
5 | # test suite. You never need to work with it otherwise. Remember that
6 | # your test database is "scratch space" for the test suite and is wiped
7 | # and recreated between test runs. Don't rely on the data there!
8 | config.cache_classes = true
9 |
10 | # Do not eager load code on boot. This avoids loading your whole application
11 | # just for the purpose of running a single test. If you are using a tool that
12 | # preloads Rails for running tests, you may have to set it to true.
13 | config.eager_load = false
14 |
15 | # Configure static file server for tests with Cache-Control for performance.
16 | config.serve_static_files = true
17 | config.static_cache_control = 'public, max-age=3600'
18 |
19 | # Show full error reports and disable caching.
20 | config.consider_all_requests_local = true
21 | config.action_controller.perform_caching = false
22 |
23 | # Raise exceptions instead of rendering exception templates.
24 | config.action_dispatch.show_exceptions = false
25 |
26 | # Disable request forgery protection in test environment.
27 | config.action_controller.allow_forgery_protection = false
28 |
29 | # Tell Action Mailer not to deliver emails to the real world.
30 | # The :test delivery method accumulates sent emails in the
31 | # ActionMailer::Base.deliveries array.
32 | config.action_mailer.delivery_method = :test
33 |
34 | # Randomize the order test cases are executed.
35 | config.active_support.test_order = :random
36 |
37 | # Print deprecation notices to the stderr.
38 | config.active_support.deprecation = :stderr
39 |
40 | # Raises error for missing translations
41 | # config.action_view.raise_on_missing_translations = true
42 | end
43 |
--------------------------------------------------------------------------------
/spec/services/item_fetcher_spec.rb:
--------------------------------------------------------------------------------
1 | require 'amazon_item_fetcher'
2 |
3 | describe Services::Amazon::ItemFetcher do
4 | before do
5 | f = File.open(Rails.root + "spec/fixtures/amazon_response1.xml")
6 | @item = Amazon::Ecs::Response.new(f).items.first
7 | @fetcher = Services::Amazon::ItemFetcher.new
8 | end
9 |
10 | it "#get_asin" do
11 | expect(@fetcher.get_asin(@item)).to eq "B0088WEN1O"
12 | end
13 |
14 | it "#get_price" do
15 | expect(@fetcher.get_price(@item)).to eq "$60.00"
16 | end
17 |
18 | describe "#get_action_url" do
19 | it "add to wishlist" do
20 | expect(@fetcher.get_action_url(@item, "Add To Wishlist")).to eq(
21 | "http://www.amazon.com/gp/registry/wishlist/add-item.html%3Fasin.0%3DB0088WEN1O%26SubscriptionId%3DAKIAIQW3RNVVQNECDDIA%26tag%3Dcodethiscod02-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3DB0088WEN1O"
22 | )
23 | end
24 |
25 | it "tell a friend" do
26 | expect(@fetcher.get_action_url(@item, "Tell A Friend")).to eq(
27 | "http://www.amazon.com/gp/pdp/taf/B0088WEN1O%3FSubscriptionId%3DAKIAIQW3RNVVQNECDDIA%26tag%3Dcodethiscod02-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3DB0088WEN1O"
28 | )
29 | end
30 | end
31 |
32 | it "#get_brand" do
33 | expect(@fetcher.get_brand(@item)).to eq "ASICS"
34 | end
35 |
36 | it "#get_url" do
37 | expect(@fetcher.get_url(@item)).to eq(
38 | "http://www.amazon.com/ASICS-GEL-VENTURE-4-M-Womens-GEL-Venture%C2%AE/dp/B0088WEN1O%3FSubscriptionId%3DAKIAIQW3RNVVQNECDDIA%26tag%3Dcodethiscod02-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3DB0088WEN1O"
39 | )
40 | end
41 |
42 | it "#get_image_url" do
43 | expect(@fetcher.get_image_url(@item)).to eq(
44 | "http://ecx.images-amazon.com/images/I/51GMGsRrXXL.jpg"
45 | )
46 | end
47 |
48 | it "#get_title" do
49 | expect(@fetcher.get_title(@item)).to eq"ASICS Women's GEL-Venture® 4"
50 | end
51 | end
52 |
--------------------------------------------------------------------------------
/spec/features/users/sign_in_spec.rb:
--------------------------------------------------------------------------------
1 | # Feature: Sign in
2 | # As a user
3 | # I want to sign in
4 | # So I can visit protected areas of the site
5 | feature 'Sign in', :devise do
6 |
7 | # Scenario: User cannot sign in if not registered
8 | # Given I do not exist as a user
9 | # When I sign in with valid credentials
10 | # Then I see an invalid credentials message
11 | scenario 'user cannot sign in if not registered' do
12 | signin('test@example.com', 'please123')
13 | expect(page).to have_content I18n.t 'devise.failure.not_found_in_database', authentication_keys: 'email'
14 | end
15 |
16 | # Scenario: User can sign in with valid credentials
17 | # Given I exist as a user
18 | # And I am not signed in
19 | # When I sign in with valid credentials
20 | # Then I see a success message
21 | scenario 'user can sign in with valid credentials' do
22 | user = FactoryGirl.create(:user)
23 | signin(user.email, user.password)
24 | expect(page).to have_content I18n.t 'devise.sessions.signed_in'
25 | end
26 |
27 | # Scenario: User cannot sign in with wrong email
28 | # Given I exist as a user
29 | # And I am not signed in
30 | # When I sign in with a wrong email
31 | # Then I see an invalid email message
32 | scenario 'user cannot sign in with wrong email' do
33 | user = FactoryGirl.create(:user)
34 | signin('invalid@email.com', user.password)
35 | expect(page).to have_content I18n.t 'devise.failure.not_found_in_database', authentication_keys: 'email'
36 | end
37 |
38 | # Scenario: User cannot sign in with wrong password
39 | # Given I exist as a user
40 | # And I am not signed in
41 | # When I sign in with a wrong password
42 | # Then I see an invalid password message
43 | scenario 'user cannot sign in with wrong password' do
44 | user = FactoryGirl.create(:user)
45 | signin(user.email, 'invalidpass')
46 | expect(page).to have_content I18n.t 'devise.failure.invalid', authentication_keys: 'email'
47 | end
48 |
49 | end
50 |
--------------------------------------------------------------------------------
/spec/controllers/api/v1/items_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Api::V1::ItemsController, :type => :controller do
4 | before do
5 | @user = create(:user)
6 |
7 | @disliked_item = create(:item)
8 | LineItem.create(item_id: @disliked_item.id, user_id: @user.id, liked: false)
9 |
10 | @liked_item = create(:item)
11 | LineItem.create(item_id: @liked_item.id, user_id: @user.id, liked: true)
12 |
13 | @unrated_item = create(:item)
14 |
15 | sign_in @user
16 | end
17 |
18 | describe '#index' do
19 | context 'when no filter is passed' do
20 | it 'returns all items' do
21 | get :index
22 | expect(response).to be_success
23 | items = JSON.parse(response.body)
24 |
25 | expect(items.length).to eq(3)
26 | expect(items[0]["id"]).to eq(@disliked_item.id)
27 | expect(items[1]["id"]).to eq(@liked_item.id)
28 | expect(items[2]["id"]).to eq(@unrated_item.id)
29 | end
30 | end
31 |
32 | context "when filter 'liked' is provided" do
33 | it 'returns only liked items' do
34 | get :index, liked: 'true'
35 | expect(response).to be_success
36 | items = JSON.parse(response.body)
37 |
38 | expect(items.length).to eq(1)
39 | expect(items[0]["id"]).to eq(@liked_item.id)
40 | end
41 | end
42 |
43 | context "when filter 'disliked' is provided" do
44 | it 'returns only disliked items' do
45 | get :index, disliked: 'true'
46 | expect(response).to be_success
47 | items = JSON.parse(response.body)
48 |
49 | expect(items.length).to eq(1)
50 | expect(items[0]["id"]).to eq(@disliked_item.id)
51 | end
52 | end
53 |
54 | context "when filter 'unrated' is provided" do
55 | it 'returns only unrated items' do
56 | get :index, unrated: 'true'
57 | expect(response).to be_success
58 | items = JSON.parse(response.body)
59 |
60 | expect(items.length).to eq(1)
61 | expect(items[0]["id"]).to eq(@unrated_item.id)
62 | end
63 | end
64 | end
65 | end
66 |
--------------------------------------------------------------------------------
/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # In the development environment your application's code is reloaded on
5 | # every request. This slows down response time but is perfect for development
6 | # since you don't have to restart the web server when you make code changes.
7 | config.cache_classes = false
8 |
9 | # Do not eager load code on boot.
10 | config.eager_load = false
11 |
12 | # Show full error reports and disable caching.
13 | config.consider_all_requests_local = true
14 | config.action_controller.perform_caching = false
15 |
16 | # Don't care if the mailer can't send.
17 | config.action_mailer.raise_delivery_errors = false
18 |
19 | # Print deprecation notices to the Rails logger.
20 | config.active_support.deprecation = :log
21 |
22 | # Raise an error on page load if there are pending migrations.
23 | config.active_record.migration_error = :page_load
24 |
25 | # Debug mode disables concatenation and preprocessing of assets.
26 | # This option may cause significant delays in view rendering with a large
27 | # number of complex assets.
28 | config.assets.debug = true
29 |
30 | config.action_mailer.smtp_settings = {
31 | address: "smtp.sendgrid.net",
32 | port: 587,
33 | domain: Rails.application.secrets.domain_name,
34 | authentication: "plain",
35 | enable_starttls_auto: true,
36 | user_name: Rails.application.secrets.email_provider_username,
37 | password: Rails.application.secrets.email_provider_password
38 | }
39 | # ActionMailer Config
40 | config.action_mailer.default_url_options = { :host => 'localhost:3000' }
41 | config.action_mailer.delivery_method = :smtp
42 | config.action_mailer.raise_delivery_errors = true
43 | # Send email in development mode?
44 | config.action_mailer.perform_deliveries = true
45 |
46 |
47 | # Asset digests allow you to set far-future HTTP expiration dates on all assets,
48 | # yet still be able to expire them through the digest params.
49 | config.assets.digest = true
50 |
51 | # Adds additional error checking when serving assets at runtime.
52 | # Checks for improperly declared sprockets dependencies.
53 | # Raises helpful error messages.
54 | config.assets.raise_runtime_errors = true
55 |
56 | # Raises error for missing translations
57 | # config.action_view.raise_on_missing_translations = true
58 | end
59 |
--------------------------------------------------------------------------------
/spec/rails_helper.rb:
--------------------------------------------------------------------------------
1 | # This file is copied to spec/ when you run 'rails generate rspec:install'
2 | ENV["RAILS_ENV"] ||= 'test'
3 | require 'spec_helper'
4 | require File.expand_path("../../config/environment", __FILE__)
5 | require 'rspec/rails'
6 | require 'pry'
7 | # Add additional requires below this line. Rails is not loaded until this point!
8 |
9 | # Requires supporting ruby files with custom matchers and macros, etc, in
10 | # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
11 | # run as spec files by default. This means that files in spec/support that end
12 | # in _spec.rb will both be required and run as specs, causing the specs to be
13 | # run twice. It is recommended that you do not name files matching this glob to
14 | # end with _spec.rb. You can configure this pattern with the --pattern
15 | # option on the command line or in ~/.rspec, .rspec or `.rspec-local`.
16 | #
17 | # The following line is provided for convenience purposes. It has the downside
18 | # of increasing the boot-up time by auto-requiring all files in the support
19 | # directory. Alternatively, in the individual `*_spec.rb` files, manually
20 | # require only the support files necessary.
21 | #
22 | Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
23 |
24 | # Checks for pending migrations before tests are run.
25 | # If you are not using ActiveRecord, you can remove this line.
26 | ActiveRecord::Migration.maintain_test_schema!
27 |
28 | RSpec.configure do |config|
29 | # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
30 | config.fixture_path = "#{::Rails.root}/spec/fixtures"
31 |
32 | # If you're not using ActiveRecord, or you'd prefer not to run each of your
33 | # examples within a transaction, remove the following line or assign false
34 | # instead of true.
35 | config.use_transactional_fixtures = false
36 |
37 | # RSpec Rails can automatically mix in different behaviours to your tests
38 | # based on their file location, for example enabling you to call `get` and
39 | # `post` in specs under `spec/controllers`.
40 | #
41 | # You can disable this behaviour by removing the line below, and instead
42 | # explicitly tag your specs with their type, e.g.:
43 | #
44 | # RSpec.describe UsersController, :type => :controller do
45 | # # ...
46 | # end
47 | #
48 | # The different available types are documented in the features, such as in
49 | # https://relishapp.com/rspec/rspec-rails/docs
50 | config.infer_spec_type_from_file_location!
51 | end
52 |
--------------------------------------------------------------------------------
/spec/models/share_queue_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe ShareQueue, :type => :model do
4 |
5 | describe 'scopes' do
6 | before do
7 | @share_queue = build(:share_queue)
8 | @user = create(:user)
9 | @friend_user = create(:user)
10 | end
11 |
12 | describe '#created_by' do
13 | before do
14 | @share_queue.recipient_id = @friend_user.id
15 | @share_queue.creator_id = @user.id
16 | @share_queue.save
17 | end
18 |
19 | it 'returns share_queues that were created by given user' do
20 | share_queues = ShareQueue.created_by(@user)
21 | expect(share_queues).to eq([@share_queue])
22 | end
23 | end
24 |
25 | describe '#received_by' do
26 | before do
27 | @share_queue.recipient_id = @user.id
28 | @share_queue.creator_id = @friend_user.id
29 | @share_queue.save
30 | end
31 |
32 | it 'returns share_queues that were created by given user' do
33 | share_queues = ShareQueue.received_by(@user)
34 | expect(share_queues).to eq([@share_queue])
35 | end
36 | end
37 | end
38 |
39 | describe '#complete' do
40 | before do
41 | @share_queue = create(:share_queue, recipient_id: create(:user).id, creator_id: create(:user).id)
42 | @line_item1 = create(:line_item, share_queue_id: @share_queue.id)
43 | @line_item2 = create(:line_item, share_queue_id: @share_queue.id)
44 | end
45 |
46 | it 'is valid' do
47 | expect(@share_queue).to be_valid
48 | end
49 |
50 | context 'all of the queues line items have been reviewed' do
51 | before do
52 | @line_item1.update_attributes liked: true
53 | @line_item2.update_attributes liked: false
54 | end
55 |
56 | it 'returns true' do
57 | expect(@share_queue.complete?).to be true
58 | end
59 | end
60 |
61 | context 'none of the queues line items have been reviewed' do
62 | before do
63 | @line_item1.update_attributes liked: nil
64 | @line_item2.update_attributes liked: nil
65 | end
66 |
67 | it 'returns false' do
68 | expect(@share_queue.complete?).to be false
69 | end
70 | end
71 |
72 | context 'some of the queues line items have been reviewed' do
73 | before do
74 | @line_item1.update_attributes liked: true
75 | @line_item2.update_attributes liked: nil
76 | end
77 |
78 | it 'returns false' do
79 | expect(@share_queue.complete?).to be false
80 | end
81 | end
82 | end
83 | end
84 |
--------------------------------------------------------------------------------
/db/seeds.rb:
--------------------------------------------------------------------------------
1 | #############
2 | # Amazon
3 | #############
4 |
5 | SearchNode.create(
6 | enabled: true,
7 | vendor_id: 679341011,
8 | category: "Basketball",
9 | parent_category: "Athletic",
10 | vendor: "Amazon"
11 | )
12 |
13 | SearchNode.create(
14 | enabled: true,
15 | vendor_id: 679360011,
16 | category: "Running",
17 | parent_category: "Athletic",
18 | vendor: "Amazon"
19 | )
20 |
21 | SearchNode.create(
22 | enabled: true,
23 | vendor_id: 679377011,
24 | category: "Walking",
25 | parent_category: "Athletic",
26 | vendor: "Amazon"
27 | )
28 |
29 | SearchNode.create(
30 | enabled: true,
31 | vendor_id: 679369011,
32 | category: "Skateboard",
33 | parent_category: "Athletic",
34 | vendor: "Amazon"
35 | )
36 |
37 | SearchNode.create(
38 | enabled: true,
39 | vendor_id: 679374011,
40 | category: "Tennis",
41 | parent_category: "Athletic",
42 | vendor: "Amazon"
43 | )
44 |
45 | SearchNode.create(
46 | enabled: true,
47 | vendor_id: 684423011,
48 | category: "Dress & Evening Flats",
49 | parent_category: "Flats",
50 | vendor: "Amazon"
51 | )
52 |
53 | SearchNode.create(
54 | enabled: true,
55 | vendor_id: 679401011,
56 | category: "Mary-Jane Flats",
57 | parent_category: "Flats",
58 | vendor: "Amazon"
59 | )
60 |
61 | SearchNode.create(
62 | enabled: true,
63 | vendor_id: 679403011,
64 | category: "Slingback Flats",
65 | parent_category: "Flats",
66 | vendor: "Amazon"
67 | )
68 |
69 | SearchNode.create(
70 | enabled: true,
71 | vendor_id: 683100011,
72 | category: "T-Strap Flats",
73 | parent_category: "Flats",
74 | vendor: "Amazon"
75 | )
76 |
77 | #############
78 | # Popshops
79 | #############
80 |
81 | SearchNode.create(
82 | enabled: true,
83 | vendor_id: 25783,
84 | category: "Athletic Shoes",
85 | parent_category: "",
86 | vendor: "Popshops"
87 | )
88 |
89 | SearchNode.create(
90 | vendor_id: 25869,
91 | category: "Boots",
92 | enabled: true,
93 | vendor: "Popshops",
94 | parent_category: ""
95 | )
96 |
97 | SearchNode.create(
98 | vendor_id: 25632,
99 | category: "Casual Shoes",
100 | enabled: true,
101 | vendor: "Popshops",
102 | parent_category: ""
103 | )
104 |
105 | SearchNode.create(
106 | vendor_id: 25703,
107 | category: "Sandals",
108 | enabled: true,
109 | vendor: "Popshops",
110 | parent_category: ""
111 | )
112 |
113 | SearchNode.create(
114 | vendor_id: 25743,
115 | category: "Slippers & Flip-Flops",
116 | enabled: true,
117 | vendor: "Popshops",
118 | parent_category: ""
119 | )
120 |
--------------------------------------------------------------------------------
/app/services/popshops_item_fetcher.rb:
--------------------------------------------------------------------------------
1 | module Services
2 | module Popshops
3 | class ItemFetcher
4 | def initialize(options)
5 | @req_opts = {
6 | query: {
7 | account: "34n38shyj4y123ngozmvhkfis",
8 | catalog: "e5o3n125egw96por86dazlaey",
9 | results_per_page: "100",
10 | category: options[:category],
11 | page: options[:page],
12 | per_page: 100
13 | }
14 | }
15 | end
16 |
17 | def do_request
18 | old_count = Item.count
19 | res = HTTParty.get("http://popshops.com/v3/products.json", @req_opts)
20 | parse_response res
21 | return Item.count - old_count
22 | end
23 |
24 | handle_asynchronously :do_request
25 |
26 | private
27 |
28 | def parse_response(res)
29 | return if res.code != 200
30 | json = items(res)
31 |
32 | json.each do |response_item|
33 |
34 | log = Log.new(
35 | success: true,
36 | page_fetched: get_page(res),
37 | total_pages: get_total_pages(res),
38 | search_node_id: @req_opts[:query][:category]
39 | )
40 |
41 | begin
42 | item = Item.find_or_create_by(asin: get_asin(response_item))
43 | item.title = get_title(response_item)
44 | item.price = get_price(response_item)
45 | item.image_url = get_image_url(response_item)
46 | item.url = get_url(response_item)
47 | item.search_node_id = @req_opts[:query][:category]
48 | # item.brand = get_brand(response_item) TODO: fix brand/brand_id
49 | item.save!
50 | rescue Exception => e
51 | log.message = e.message
52 | log.success = false
53 | end
54 |
55 | log.save
56 | end
57 | end
58 |
59 | def get_page(res)
60 | res["parameters"].select { |param| param["name"] == "page" }.first["value"]
61 | end
62 |
63 | def get_total_pages(res)
64 | (res["results"]["products"]["count"] / @req_opts[:query][:per_page]).ceil
65 | end
66 |
67 | def items(res)
68 | JSON.parse(res.body)["results"]["products"]["product"]
69 | end
70 |
71 | def get_title(item)
72 | item["name"]
73 | end
74 |
75 | def get_brand_id(item)
76 |
77 | end
78 |
79 | def get_asin(item)
80 | item["id"]
81 | end
82 |
83 | def get_url(item)
84 | item["offers"]["offer"].first["url"]
85 | end
86 |
87 | def get_price(item)
88 | item["offers"]["offer"].first["price_merchant"]
89 | end
90 |
91 | def get_image_url(item)
92 | item["image_url_large"]
93 | end
94 | end
95 | end
96 | end
97 |
--------------------------------------------------------------------------------
/spec/features/visitors/sign_up_spec.rb:
--------------------------------------------------------------------------------
1 | # Feature: Sign up
2 | # As a visitor
3 | # I want to sign up
4 | # So I can visit protected areas of the site
5 | feature 'Sign Up', :devise do
6 |
7 | # Scenario: Visitor can sign up with valid email address and password
8 | # Given I am not signed in
9 | # When I sign up with a valid email address and password
10 | # Then I see a successful sign up message
11 | scenario 'visitor can sign up with valid email address and password' do
12 | sign_up_with('test@example.com', 'please123', 'please123')
13 | txts = [I18n.t( 'devise.registrations.signed_up'), I18n.t( 'devise.registrations.signed_up_but_unconfirmed')]
14 | expect(page).to have_content(/.*#{txts[0]}.*|.*#{txts[1]}.*/)
15 | end
16 |
17 | # Scenario: Visitor cannot sign up with invalid email address
18 | # Given I am not signed in
19 | # When I sign up with an invalid email address
20 | # Then I see an invalid email message
21 | scenario 'visitor cannot sign up with invalid email address' do
22 | sign_up_with('bogus', 'please123', 'please123')
23 | expect(page).to have_content 'Email is invalid'
24 | end
25 |
26 | # Scenario: Visitor cannot sign up without password
27 | # Given I am not signed in
28 | # When I sign up without a password
29 | # Then I see a missing password message
30 | scenario 'visitor cannot sign up without password' do
31 | sign_up_with('test@example.com', '', '')
32 | expect(page).to have_content "Password can't be blank"
33 | end
34 |
35 | # Scenario: Visitor cannot sign up with a short password
36 | # Given I am not signed in
37 | # When I sign up with a short password
38 | # Then I see a 'too short password' message
39 | scenario 'visitor cannot sign up with a short password' do
40 | sign_up_with('test@example.com', 'please', 'please')
41 | expect(page).to have_content "Password is too short"
42 | end
43 |
44 | # Scenario: Visitor cannot sign up without password confirmation
45 | # Given I am not signed in
46 | # When I sign up without a password confirmation
47 | # Then I see a missing password confirmation message
48 | scenario 'visitor cannot sign up without password confirmation' do
49 | sign_up_with('test@example.com', 'please123', '')
50 | expect(page).to have_content "Password confirmation doesn't match"
51 | end
52 |
53 | # Scenario: Visitor cannot sign up with mismatched password and confirmation
54 | # Given I am not signed in
55 | # When I sign up with a mismatched password confirmation
56 | # Then I should see a mismatched password message
57 | scenario 'visitor cannot sign up with mismatched password and confirmation' do
58 | sign_up_with('test@example.com', 'please123', 'mismatch')
59 | expect(page).to have_content "Password confirmation doesn't match"
60 | end
61 |
62 | end
63 |
--------------------------------------------------------------------------------
/config/deploy.rb:
--------------------------------------------------------------------------------
1 | server '104.236.128.142', port: 22, roles: [:web, :app, :db], primary: true
2 |
3 | set :repo_url, 'git@github.com:StephenGrider/amzn.git'
4 | set :application, 'amzn'
5 | set :user, 'root'
6 | set :puma_threads, [4, 16]
7 | set :puma_workers, 0
8 |
9 | # Don't change these unless you know what you're doing
10 | set :pty, true
11 | set :use_sudo, false
12 | set :stage, :production
13 | set :deploy_via, :remote_cache
14 | set :deploy_to, "/home/#{fetch(:user)}/apps/#{fetch(:application)}"
15 | set :puma_bind, "unix://#{shared_path}/tmp/sockets/#{fetch(:application)}-puma.sock"
16 | set :puma_state, "#{shared_path}/tmp/pids/puma.state"
17 | set :puma_pid, "#{shared_path}/tmp/pids/puma.pid"
18 | set :puma_access_log, "#{release_path}/log/puma.error.log"
19 | set :puma_error_log, "#{release_path}/log/puma.access.log"
20 | set :ssh_options, { forward_agent: true, user: fetch(:user), keys: %w(~/.ssh/id_rsa.pub) }
21 | set :puma_preload_app, true
22 | set :puma_worker_timeout, nil
23 | set :puma_init_active_record, true # Change to false when not using ActiveRecord
24 |
25 | ## Defaults:
26 | # set :scm, :git
27 | # set :branch, :master
28 | # set :format, :pretty
29 | # set :log_level, :debug
30 | # set :keep_releases, 5
31 |
32 | ## Linked Files & Directories (Default None):
33 | # set :linked_files, %w{config/database.yml}
34 | # set :linked_dirs, %w{bin log tmp/pids tmp/cache tmp/sockets vendor/bundle public/system}
35 |
36 | namespace :puma do
37 | desc 'Create Directories for Puma Pids and Socket'
38 | task :make_dirs do
39 | on roles(:app) do
40 | execute "mkdir #{shared_path}/tmp/sockets -p"
41 | execute "mkdir #{shared_path}/tmp/pids -p"
42 | end
43 | end
44 |
45 | before :start, :make_dirs
46 | end
47 |
48 | namespace :deploy do
49 | desc "Make sure local git is in sync with remote."
50 | task :check_revision do
51 | on roles(:app) do
52 | unless `git rev-parse HEAD` == `git rev-parse origin/master`
53 | puts "WARNING: HEAD is not the same as origin/master"
54 | puts "Run `git push` to sync changes."
55 | exit
56 | end
57 | end
58 | end
59 |
60 | desc 'Initial Deploy'
61 | task :initial do
62 | on roles(:app) do
63 | before 'deploy:restart', 'puma:start'
64 | invoke 'deploy'
65 | end
66 | end
67 |
68 | desc 'Restart application'
69 | task :restart do
70 | on roles(:app), in: :sequence, wait: 5 do
71 | invoke 'puma:restart'
72 | end
73 | end
74 |
75 | before :starting, :check_revision
76 | # after :finishing, :compile_assets
77 | # after :finishing, :cleanup
78 | after :finishing, 'deploy:restart'
79 | end
80 |
81 | # ps aux | grep puma # Get puma pid
82 | # kill -s SIGUSR2 pid # Restart puma
83 | # kill -s SIGTERM pid # Stop puma
84 |
--------------------------------------------------------------------------------
/config/deploy/production.rb:
--------------------------------------------------------------------------------
1 | server '104.236.128.142', port: 22, roles: [:web, :app, :db], primary: true
2 |
3 | set :repo_url, 'git@github.com:StephenGrider/amzn.git'
4 | set :application, 'amzn'
5 | set :user, 'root'
6 | set :puma_threads, [4, 16]
7 | set :puma_workers, 0
8 |
9 | # Don't change these unless you know what you're doing
10 | set :pty, true
11 | set :use_sudo, false
12 | set :stage, :production
13 | set :deploy_via, :remote_cache
14 | set :deploy_to, "/home/#{fetch(:user)}/apps/#{fetch(:application)}"
15 | set :puma_bind, "unix://#{shared_path}/tmp/sockets/#{fetch(:application)}-puma.sock"
16 | set :puma_state, "#{shared_path}/tmp/pids/puma.state"
17 | set :puma_pid, "#{shared_path}/tmp/pids/puma.pid"
18 | set :puma_access_log, "#{release_path}/log/puma.error.log"
19 | set :puma_error_log, "#{release_path}/log/puma.access.log"
20 | set :ssh_options, { forward_agent: true, user: fetch(:user), keys: %w(~/.ssh/id_rsa.pub) }
21 | set :puma_preload_app, true
22 | set :puma_worker_timeout, nil
23 | set :puma_init_active_record, true # Change to false when not using ActiveRecord
24 |
25 | ## Defaults:
26 | # set :scm, :git
27 | # set :branch, :master
28 | # set :format, :pretty
29 | # set :log_level, :debug
30 | # set :keep_releases, 5
31 |
32 | ## Linked Files & Directories (Default None):
33 | # set :linked_files, %w{config/database.yml}
34 | # set :linked_dirs, %w{bin log tmp/pids tmp/cache tmp/sockets vendor/bundle public/system}
35 |
36 | namespace :puma do
37 | desc 'Create Directories for Puma Pids and Socket'
38 | task :make_dirs do
39 | on roles(:app) do
40 | execute "mkdir #{shared_path}/tmp/sockets -p"
41 | execute "mkdir #{shared_path}/tmp/pids -p"
42 | end
43 | end
44 |
45 | before :start, :make_dirs
46 | end
47 |
48 | namespace :deploy do
49 | desc "Make sure local git is in sync with remote."
50 | task :check_revision do
51 | on roles(:app) do
52 | unless `git rev-parse HEAD` == `git rev-parse origin/master`
53 | puts "WARNING: HEAD is not the same as origin/master"
54 | puts "Run `git push` to sync changes."
55 | exit
56 | end
57 | end
58 | end
59 |
60 | desc 'Initial Deploy'
61 | task :initial do
62 | on roles(:app) do
63 | before 'deploy:restart', 'puma:start'
64 | invoke 'deploy'
65 | end
66 | end
67 |
68 | desc 'Restart application'
69 | task :restart do
70 | on roles(:app), in: :sequence, wait: 5 do
71 | invoke 'puma:restart'
72 | end
73 | end
74 |
75 | before :starting, :check_revision
76 | # after :finishing, :compile_assets
77 | after :finishing, 'deploy:cleanup'
78 | after :finishing, 'deploy:restart'
79 | end
80 |
81 | # ps aux | grep puma # Get puma pid
82 | # kill -s SIGUSR2 pid # Restart puma
83 | # kill -s SIGTERM pid # Stop puma
84 |
--------------------------------------------------------------------------------
/spec/controllers/api/v1/line_items_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Api::V1::LineItemsController, :type => :controller do
4 |
5 | before do
6 | @user = create(:user)
7 | sign_in @user
8 | end
9 |
10 | describe '#index' do
11 | describe 'unfiltered' do
12 | it 'returns all line_items' do
13 | FactoryGirl.create_list(:line_item, 5)
14 |
15 | get :index
16 |
17 | parsed_response = JSON.parse(response.body)
18 | expect(parsed_response.length).to eq(5)
19 | expect(parsed_response.first['user_id']).to_not be_nil
20 | expect(parsed_response.first['item_id']).to_not be_nil
21 | end
22 | end
23 | end
24 |
25 | describe '#create' do
26 | context 'valid attributes' do
27 | attrs = nil
28 |
29 | before do
30 | attrs = FactoryGirl.attributes_for(:line_item)
31 | .merge(item_id: create(:item).id)
32 | end
33 |
34 | it 'creates a new line_item' do
35 | expect{
36 | post :create, attrs
37 | }.to change(LineItem, :count).by(1)
38 | end
39 |
40 | it 'returns the new model' do
41 | post :create, attrs
42 |
43 | parsed_response = JSON.parse(response.body)
44 | expect(parsed_response["user_id"]).to eq(@user.id)
45 | expect(parsed_response["item_id"]).to eq(attrs[:item_id])
46 | end
47 | end
48 |
49 | context 'invalid attributes' do
50 | attrs = nil
51 |
52 | before do
53 | attrs = FactoryGirl.attributes_for(:line_item)
54 | attrs[:user_id] = nil
55 | end
56 |
57 | it 'does not create a new line_item' do
58 | expect{
59 | post :create, attrs
60 | }.to change(LineItem, :count).by(0)
61 | end
62 |
63 | it '422s' do
64 | post :create, attrs
65 | expect(response.status).to eq(422)
66 | end
67 | end
68 | end
69 |
70 | describe '#update' do
71 | before do
72 | @line_item = FactoryGirl.create(:line_item, user_id: @user.id, liked: true)
73 | end
74 |
75 | it 'updates the model' do
76 | put :update, id: @line_item.id, liked: false
77 | @line_item.reload
78 | expect(@line_item.liked).to be false
79 | end
80 | end
81 |
82 | describe '#destroy' do
83 | before do
84 | @line_item = FactoryGirl.create(:line_item, user_id: @user.id)
85 | end
86 |
87 | it 'destroys the model' do
88 | expect{
89 | delete :destroy, id: @line_item.id
90 | }.to change(LineItem, :count).by(-1)
91 | end
92 |
93 | it 'renders the model' do
94 | delete :destroy, id: @line_item.id
95 |
96 | parsed_response = JSON.parse(response.body)
97 | expect(parsed_response["user_id"]).to eq(@line_item.user_id)
98 | expect(parsed_response["item_id"]).to eq(@line_item.item_id)
99 | end
100 | end
101 | end
102 |
--------------------------------------------------------------------------------
/spec/controllers/api/v1/share_queues_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Api::V1::ShareQueuesController, :type => :controller do
4 | before do
5 | @user = create(:user)
6 | sign_in @user
7 | end
8 |
9 | describe '#index' do
10 | before do
11 | @friend = create(:user)
12 | @created_share_queue = create(:share_queue, creator_id: @user.id, recipient_id: @friend.id)
13 | @received_share_queue = create(:share_queue, creator_id: @friend.id, recipient_id: @user.id)
14 | end
15 |
16 | context 'with no filters' do
17 | it 'returns all share queues related to the current user' do
18 | get :index
19 |
20 | share_queues = JSON.parse(response.body)
21 | expect(share_queues.length).to eq(2)
22 | end
23 | end
24 |
25 | context 'with creator filter' do
26 | it 'returns share_queues created by the current user' do
27 | get :index, created: true
28 | share_queues = JSON.parse(response.body)
29 | expect(share_queues.length).to eq(1)
30 | expect(share_queues[0]["id"]).to eq(@created_share_queue.id)
31 | end
32 | end
33 |
34 | context 'with recieved filter' do
35 | it 'returns share_queues received by the current user' do
36 | get :index, received: true
37 | share_queues = JSON.parse(response.body)
38 | expect(share_queues.length).to eq(1)
39 | expect(share_queues[0]["id"]).to eq(@received_share_queue.id)
40 | end
41 | end
42 | end
43 |
44 | describe '#create' do
45 | before do
46 | @friend = create(:user)
47 | @item = create(:item)
48 |
49 | @attrs = {
50 | creator_id: @user.id,
51 | recipient_id: @friend.id,
52 | item_ids: [@item.id]
53 | }
54 | end
55 |
56 | it 'creates a new queue' do
57 | expect {
58 | post :create, @attrs
59 | }.to change(ShareQueue, :count).by(1)
60 | end
61 |
62 | it 'creates a queue with correct attributes' do
63 | post :create, @attrs
64 |
65 | parsed_response = JSON.parse(response.body)
66 | expect(parsed_response["creator_id"]).to eq(@user.id)
67 | expect(parsed_response["recipient_id"]).to eq(@friend.id)
68 | end
69 |
70 | it 'creates a line_item' do
71 | expect {
72 | post :create, @attrs
73 | }.to change(LineItem, :count).by(1)
74 |
75 | expect(LineItem.all.last.item_id).to eq(@item.id)
76 | end
77 |
78 | context 'recipient has already reviewed this item' do
79 | before do
80 | @line_item = LineItem.create(user_id: @friend.id, item_id: @item.id, liked: true)
81 | end
82 |
83 | it 'clears the liked property' do
84 | expect {
85 | post :create, @attrs
86 | }.to change(LineItem, :count).by(0)
87 |
88 | @line_item.reload
89 | expect(@line_item.liked).to be_nil
90 | expect(ShareQueue.all.last.line_items[0]).to eq(@line_item)
91 | end
92 | end
93 | end
94 | end
95 |
--------------------------------------------------------------------------------
/app/services/amazon_item_fetcher.rb:
--------------------------------------------------------------------------------
1 | module Services
2 | module Amazon
3 | class ItemFetcher
4 | def fetch_items(options)
5 | res = Amazon::Ecs.item_search(options[:search_string],
6 | { response_group: 'OfferFull,Images,ItemAttributes', browse_node: options[:node_id] })
7 |
8 | log_empty_response(options) if res.items.length == 0
9 |
10 | create_items(res, options[:node_id])
11 | end
12 |
13 | def create_items(res, node_id)
14 | res.items.each do |response_item|
15 | item = Item.find_or_create_by(asin: get_asin(response_item))
16 |
17 | log = Log.new(
18 | success: true,
19 | total_pages: res.total_pages,
20 | page_fetched: res.item_page,
21 | search_node_id: SearchNode.find_by_amazon_id(node_id).id
22 | )
23 |
24 | begin
25 | log.asin = get_asin(response_item)
26 | item.title = get_title(response_item)
27 | item.image_url = get_image_url(response_item)
28 | item.url = get_url(response_item)
29 | item.brand = get_brand(response_item)
30 | item.price = get_price(response_item)
31 | item.wishlist_url = get_action_url(response_item, "Add To Wishlist")
32 | item.tell_friend_url = get_action_url(response_item, "Tell A Friend")
33 | item.search_node_id = SearchNode.find_by_amazon_id(node_id).id
34 | item.save!
35 | rescue Exception => e
36 | log.message = e.message
37 | log.success = false
38 | end
39 |
40 | log.save
41 | end
42 | end
43 |
44 | def get_asin(item)
45 | item.get_element('ASIN').get
46 | end
47 |
48 | def get_price(item)
49 | item.get_element('ListPrice').get('FormattedPrice')
50 | end
51 |
52 | def get_action_url(item, type)
53 | url = ''
54 | links = Hash.from_xml(item.get_element('ItemLinks').to_s).deep_symbolize_keys
55 | item_link = links.fetch(:ItemLinks).fetch(:ItemLink)
56 |
57 | if item_link.respond_to?(:has_key?) && item_link.has_key?(:element)
58 | item_link = item_link.fetch(:element)
59 | end
60 |
61 | item_link.each do |link|
62 | url = link[:URL] if link.fetch(:Description) == type
63 | end
64 |
65 | url
66 | end
67 |
68 | def get_brand(item)
69 | item.get_element('ItemAttributes').get('Brand')
70 | end
71 |
72 | def get_url(item)
73 | item.get_element('DetailPageURL').get
74 | end
75 |
76 | def get_image_url(item)
77 | item.get_element('LargeImage').get('URL')
78 | end
79 |
80 | def get_title(item)
81 | item.get_element('ItemAttributes').get('Title')
82 | end
83 |
84 | def log_empty_response(options)
85 | Log.create(
86 | json: options.to_json,
87 | message: "Empty response"
88 | )
89 | end
90 | end
91 | end
92 | end
93 |
--------------------------------------------------------------------------------
/Guardfile:
--------------------------------------------------------------------------------
1 | # A sample Guardfile
2 | # More info at https://github.com/guard/guard#readme
3 |
4 | ## Uncomment and set this to only include directories you want to watch
5 | # directories %w(app lib config test spec feature)
6 |
7 | ## Uncomment to clear the screen before every task
8 | # clearing :on
9 |
10 | ## Guard internally checks for changes in the Guardfile and exits.
11 | ## If you want Guard to automatically start up again, run guard in a
12 | ## shell loop, e.g.:
13 | ##
14 | ## $ while bundle exec guard; do echo "Restarting Guard..."; done
15 | ##
16 | ## Note: if you are using the `directories` clause above and you are not
17 | ## watching the project directory ('.'), the you will want to move the Guardfile
18 | ## to a watched dir and symlink it back, e.g.
19 | #
20 | # $ mkdir config
21 | # $ mv Guardfile config/
22 | # $ ln -s config/Guardfile .
23 | #
24 | # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
25 |
26 | guard :bundler do
27 | require 'guard/bundler'
28 | require 'guard/bundler/verify'
29 | helper = Guard::Bundler::Verify.new
30 |
31 | files = ['Gemfile']
32 | files += Dir['*.gemspec'] if files.any? { |f| helper.uses_gemspec?(f) }
33 |
34 | # Assume files are symlinked from somewhere
35 | files.each { |file| watch(helper.real_path(file)) }
36 | end
37 |
38 | guard 'rails' do
39 | watch('Gemfile.lock')
40 | watch(%r{^(config|lib)/.*})
41 | end
42 |
43 |
44 | # Note: The cmd option is now required due to the increasing number of ways
45 | # rspec may be run, below are examples of the most common uses.
46 | # * bundler: 'bundle exec rspec'
47 | # * bundler binstubs: 'bin/rspec'
48 | # * spring: 'bin/rspec' (This will use spring if running and you have
49 | # installed the spring binstubs per the docs)
50 | # * zeus: 'zeus rspec' (requires the server to be started separately)
51 | # * 'just' rspec: 'rspec'
52 |
53 | guard :rspec, cmd: "bundle exec rspec" do
54 | require "guard/rspec/dsl"
55 | dsl = Guard::RSpec::Dsl.new(self)
56 |
57 | # Feel free to open issues for suggestions and improvements
58 |
59 | # RSpec files
60 | rspec = dsl.rspec
61 | watch(rspec.spec_helper) { rspec.spec_dir }
62 | watch(rspec.spec_support) { rspec.spec_dir }
63 | watch(rspec.spec_files)
64 |
65 | # Ruby files
66 | ruby = dsl.ruby
67 | dsl.watch_spec_files_for(ruby.lib_files)
68 |
69 | # Rails files
70 | rails = dsl.rails(view_extensions: %w(erb haml slim))
71 | dsl.watch_spec_files_for(rails.app_files)
72 | dsl.watch_spec_files_for(rails.views)
73 |
74 | watch(rails.controllers) do |m|
75 | [
76 | rspec.spec.("routing/#{m[1]}_routing"),
77 | rspec.spec.("controllers/#{m[1]}_controller"),
78 | rspec.spec.("acceptance/#{m[1]}")
79 | ]
80 | end
81 |
82 | # Rails config changes
83 | watch(rails.spec_helper) { rspec.spec_dir }
84 | watch(rails.routes) { "#{rspec.spec_dir}/routing" }
85 | watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
86 |
87 | # Capybara features specs
88 | watch(rails.view_dirs) { |m| rspec.spec.("features/#{m[1]}") }
89 |
90 | # Turnip features and steps
91 | watch(%r{^spec/acceptance/(.+)\.feature$})
92 | watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
93 | Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
94 | end
95 | end
96 |
--------------------------------------------------------------------------------
/spec/fixtures/amazon_response2.xml:
--------------------------------------------------------------------------------
1 |
2 | -
3 |
B00M97JRIS
4 | http://www.amazon.com/Hee-Grand-Diamond-Platform-Sandals/dp/B00M97JRIS%3FSubscriptionId%3DAKIAIQW3RNVVQNECDDIA%26tag%3Dcodethiscod02-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3DB00M97JRIS
5 |
6 | Apparel
7 | Hee Grand
8 | womens
9 |
10 | Platform:3.5cm
11 | Brand New,but WITHOUT box,because box is so heavy that will add shipping fee
12 | Due to the computer's monitor , the color maybe a little different , please understand !
13 |
14 | Shoes
15 | SHOES
16 | Hee Grand Women Diamond Platform High Heel Sandals
17 |
18 |
19 |
20 |
21 | Technical Details
22 | http://www.amazon.com/Hee-Grand-Diamond-Platform-Sandals/dp/tech-data/B00M97JRIS%3FSubscriptionId%3DAKIAIQW3RNVVQNECDDIA%26tag%3Dcodethiscod02-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3DB00M97JRIS
23 |
24 |
25 | Add To Baby Registry
26 | http://www.amazon.com/gp/registry/baby/add-item.html%3Fasin.0%3DB00M97JRIS%26SubscriptionId%3DAKIAIQW3RNVVQNECDDIA%26tag%3Dcodethiscod02-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3DB00M97JRIS
27 |
28 |
29 | Add To Wedding Registry
30 | http://www.amazon.com/gp/registry/wedding/add-item.html%3Fasin.0%3DB00M97JRIS%26SubscriptionId%3DAKIAIQW3RNVVQNECDDIA%26tag%3Dcodethiscod02-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3DB00M97JRIS
31 |
32 |
33 | Add To Wishlist
34 | http://www.amazon.com/gp/registry/wishlist/add-item.html%3Fasin.0%3DB00M97JRIS%26SubscriptionId%3DAKIAIQW3RNVVQNECDDIA%26tag%3Dcodethiscod02-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3DB00M97JRIS
35 |
36 |
37 | Tell A Friend
38 | http://www.amazon.com/gp/pdp/taf/B00M97JRIS%3FSubscriptionId%3DAKIAIQW3RNVVQNECDDIA%26tag%3Dcodethiscod02-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3DB00M97JRIS
39 |
40 |
41 | All Customer Reviews
42 | http://www.amazon.com/review/product/B00M97JRIS%3FSubscriptionId%3DAKIAIQW3RNVVQNECDDIA%26tag%3Dcodethiscod02-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3DB00M97JRIS
43 |
44 |
45 | All Offers
46 | http://www.amazon.com/gp/offer-listing/B00M97JRIS%3FSubscriptionId%3DAKIAIQW3RNVVQNECDDIA%26tag%3Dcodethiscod02-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3DB00M97JRIS
47 |
48 |
49 |
50 |
51 | 0
52 | 0
53 | 0
54 | 0
55 |
56 |
57 | 0
58 | 0
59 | 0
60 |
61 | B00M97JRIS
62 |
63 |
--------------------------------------------------------------------------------
/config/locales/devise.en.yml:
--------------------------------------------------------------------------------
1 | # Additional translations at https://github.com/plataformatec/devise/wiki/I18n
2 |
3 | en:
4 | devise:
5 | confirmations:
6 | confirmed: "Your email address has been successfully confirmed."
7 | send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes."
8 | send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes."
9 | failure:
10 | already_authenticated: "You are already signed in."
11 | inactive: "Your account is not activated yet."
12 | invalid: "Invalid %{authentication_keys} or password."
13 | locked: "Your account is locked."
14 | last_attempt: "You have one more attempt before your account is locked."
15 | not_found_in_database: "Invalid %{authentication_keys} or password."
16 | timeout: "Your session expired. Please sign in again to continue."
17 | unauthenticated: "You need to sign in or sign up before continuing."
18 | unconfirmed: "You have to confirm your email address before continuing."
19 | mailer:
20 | confirmation_instructions:
21 | subject: "Confirmation instructions"
22 | reset_password_instructions:
23 | subject: "Reset password instructions"
24 | unlock_instructions:
25 | subject: "Unlock instructions"
26 | omniauth_callbacks:
27 | failure: "Could not authenticate you from %{kind} because \"%{reason}\"."
28 | success: "Successfully authenticated from %{kind} account."
29 | passwords:
30 | no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided."
31 | send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes."
32 | send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes."
33 | updated: "Your password has been changed successfully. You are now signed in."
34 | updated_not_active: "Your password has been changed successfully."
35 | registrations:
36 | destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon."
37 | signed_up: "Welcome! You have signed up successfully."
38 | signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated."
39 | signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked."
40 | signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account."
41 | update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirm link to confirm your new email address."
42 | updated: "Your account has been updated successfully."
43 | sessions:
44 | signed_in: "Signed in successfully."
45 | signed_out: "Signed out successfully."
46 | already_signed_out: "Signed out successfully."
47 | unlocks:
48 | send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes."
49 | send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes."
50 | unlocked: "Your account has been unlocked successfully. Please sign in to continue."
51 | errors:
52 | messages:
53 | already_confirmed: "was already confirmed, please try signing in"
54 | confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one"
55 | expired: "has expired, please request a new one"
56 | not_found: "not found"
57 | not_locked: "was not locked"
58 | not_saved:
59 | one: "1 error prohibited this %{resource} from being saved:"
60 | other: "%{count} errors prohibited this %{resource} from being saved:"
61 |
--------------------------------------------------------------------------------
/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # Code is not reloaded between requests.
5 | config.cache_classes = true
6 |
7 | # Eager load code on boot. This eager loads most of Rails and
8 | # your application in memory, allowing both threaded web servers
9 | # and those relying on copy on write to perform better.
10 | # Rake tasks automatically ignore this option for performance.
11 | config.eager_load = true
12 |
13 | # Full error reports are disabled and caching is turned on.
14 | config.consider_all_requests_local = false
15 | config.action_controller.perform_caching = true
16 |
17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application
18 | # Add `rack-cache` to your Gemfile before enabling this.
19 | # For large-scale production use, consider using a caching reverse proxy like
20 | # NGINX, varnish or squid.
21 | # config.action_dispatch.rack_cache = true
22 |
23 | # Disable serving static files from the `/public` folder by default since
24 | # Apache or NGINX already handles this.
25 | config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present?
26 |
27 | # Compress JavaScripts and CSS.
28 | config.assets.js_compressor = :uglifier
29 | # config.assets.css_compressor = :sass
30 |
31 | # Do not fallback to assets pipeline if a precompiled asset is missed.
32 | config.assets.compile = false
33 |
34 | # Asset digests allow you to set far-future HTTP expiration dates on all assets,
35 | # yet still be able to expire them through the digest params.
36 | config.assets.digest = true
37 |
38 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
39 |
40 | # Specifies the header that your server uses for sending files.
41 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
42 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
43 |
44 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
45 | # config.force_ssl = true
46 |
47 | # Use the lowest log level to ensure availability of diagnostic information
48 | # when problems arise.
49 | config.log_level = :debug
50 |
51 | # Prepend all log lines with the following tags.
52 | # config.log_tags = [ :subdomain, :uuid ]
53 |
54 | # Use a different logger for distributed setups.
55 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
56 |
57 | # Use a different cache store in production.
58 | # config.cache_store = :mem_cache_store
59 |
60 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
61 | # config.action_controller.asset_host = 'http://assets.example.com'
62 |
63 | # Ignore bad email addresses and do not raise email delivery errors.
64 | # Set this to true and configure the email server for immediate delivery to raise delivery errors.
65 | # config.action_mailer.raise_delivery_errors = false
66 |
67 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
68 | # the I18n.default_locale when a translation cannot be found).
69 | config.i18n.fallbacks = true
70 |
71 | # Send deprecation notices to registered listeners.
72 | config.active_support.deprecation = :notify
73 |
74 | config.action_mailer.smtp_settings = {
75 | address: "smtp.sendgrid.net",
76 | port: 587,
77 | domain: Rails.application.secrets.domain_name,
78 | authentication: "plain",
79 | enable_starttls_auto: true,
80 | user_name: Rails.application.secrets.email_provider_username,
81 | password: Rails.application.secrets.email_provider_password
82 | }
83 | # ActionMailer Config
84 | config.action_mailer.default_url_options = { :host => Rails.application.secrets.domain_name }
85 | config.action_mailer.delivery_method = :smtp
86 | config.action_mailer.perform_deliveries = true
87 | config.action_mailer.raise_delivery_errors = false
88 |
89 |
90 | # Use default logging formatter so that PID and timestamp are not suppressed.
91 | config.log_formatter = ::Logger::Formatter.new
92 |
93 | # Do not dump schema after migrations.
94 | config.active_record.dump_schema_after_migration = false
95 | end
96 |
--------------------------------------------------------------------------------
/db/schema.rb:
--------------------------------------------------------------------------------
1 | # encoding: UTF-8
2 | # This file is auto-generated from the current state of the database. Instead
3 | # of editing this file, please use the migrations feature of Active Record to
4 | # incrementally modify your database, and then regenerate this schema definition.
5 | #
6 | # Note that this schema.rb definition is the authoritative source for your
7 | # database schema. If you need to create the application database on another
8 | # system, you should be using db:schema:load, not running all the migrations
9 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations
10 | # you'll amass, the slower it'll run and the greater likelihood for issues).
11 | #
12 | # It's strongly recommended that you check this file into your version control system.
13 |
14 | ActiveRecord::Schema.define(version: 20150415160133) do
15 |
16 | create_table "delayed_jobs", force: :cascade do |t|
17 | t.integer "priority", limit: 4, default: 0, null: false
18 | t.integer "attempts", limit: 4, default: 0, null: false
19 | t.text "handler", limit: 65535, null: false
20 | t.text "last_error", limit: 65535
21 | t.datetime "run_at"
22 | t.datetime "locked_at"
23 | t.datetime "failed_at"
24 | t.string "locked_by", limit: 255
25 | t.string "queue", limit: 255
26 | t.datetime "created_at"
27 | t.datetime "updated_at"
28 | end
29 |
30 | add_index "delayed_jobs", ["priority", "run_at"], name: "delayed_jobs_priority", using: :btree
31 |
32 | create_table "items", force: :cascade do |t|
33 | t.string "description", limit: 255
34 | t.string "price", limit: 255
35 | t.string "image_url", limit: 255
36 | t.string "url", limit: 255
37 | t.datetime "created_at", null: false
38 | t.datetime "updated_at", null: false
39 | t.string "title", limit: 255
40 | t.string "asin", limit: 255
41 | t.string "brand", limit: 255
42 | t.string "wishlist_url", limit: 255
43 | t.string "tell_friend_url", limit: 255
44 | t.integer "search_node_id", limit: 4
45 | end
46 |
47 | create_table "line_items", force: :cascade do |t|
48 | t.datetime "created_at", null: false
49 | t.datetime "updated_at", null: false
50 | t.integer "item_id", limit: 4
51 | t.integer "user_id", limit: 4
52 | t.boolean "liked", limit: 1
53 | t.integer "share_queue_id", limit: 4
54 | end
55 |
56 | add_index "line_items", ["item_id"], name: "index_line_items_on_item_id", using: :btree
57 | add_index "line_items", ["user_id"], name: "index_line_items_on_user_id", using: :btree
58 |
59 | create_table "logs", force: :cascade do |t|
60 | t.text "json", limit: 65535
61 | t.text "message", limit: 65535
62 | t.text "asin", limit: 65535
63 | t.integer "page_fetched", limit: 4
64 | t.integer "total_pages", limit: 4
65 | t.boolean "success", limit: 1
66 | t.datetime "created_at", null: false
67 | t.datetime "updated_at", null: false
68 | t.integer "search_node_id", limit: 4
69 | end
70 |
71 | create_table "search_nodes", force: :cascade do |t|
72 | t.boolean "enabled", limit: 1
73 | t.string "category", limit: 255
74 | t.string "parent_category", limit: 255
75 | t.integer "vendor_id", limit: 4
76 | t.datetime "created_at", null: false
77 | t.datetime "updated_at", null: false
78 | t.string "vendor", limit: 255
79 | end
80 |
81 | create_table "share_queues", force: :cascade do |t|
82 | t.datetime "created_at", null: false
83 | t.datetime "updated_at", null: false
84 | t.integer "creator_id", limit: 4
85 | t.integer "recipient_id", limit: 4
86 | end
87 |
88 | create_table "users", force: :cascade do |t|
89 | t.string "email", limit: 255, default: "", null: false
90 | t.string "encrypted_password", limit: 255, default: "", null: false
91 | t.string "reset_password_token", limit: 255
92 | t.datetime "reset_password_sent_at"
93 | t.datetime "remember_created_at"
94 | t.integer "sign_in_count", limit: 4, default: 0, null: false
95 | t.datetime "current_sign_in_at"
96 | t.datetime "last_sign_in_at"
97 | t.string "current_sign_in_ip", limit: 255
98 | t.string "last_sign_in_ip", limit: 255
99 | t.datetime "created_at"
100 | t.datetime "updated_at"
101 | t.string "name", limit: 255
102 | t.string "guid", limit: 255
103 | t.boolean "admin", limit: 1
104 | end
105 |
106 | add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
107 | add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
108 |
109 | end
110 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | actionmailer (4.2.0)
5 | actionpack (= 4.2.0)
6 | actionview (= 4.2.0)
7 | activejob (= 4.2.0)
8 | mail (~> 2.5, >= 2.5.4)
9 | rails-dom-testing (~> 1.0, >= 1.0.5)
10 | actionpack (4.2.0)
11 | actionview (= 4.2.0)
12 | activesupport (= 4.2.0)
13 | rack (~> 1.6.0)
14 | rack-test (~> 0.6.2)
15 | rails-dom-testing (~> 1.0, >= 1.0.5)
16 | rails-html-sanitizer (~> 1.0, >= 1.0.1)
17 | actionview (4.2.0)
18 | activesupport (= 4.2.0)
19 | builder (~> 3.1)
20 | erubis (~> 2.7.0)
21 | rails-dom-testing (~> 1.0, >= 1.0.5)
22 | rails-html-sanitizer (~> 1.0, >= 1.0.1)
23 | activejob (4.2.0)
24 | activesupport (= 4.2.0)
25 | globalid (>= 0.3.0)
26 | activemodel (4.2.0)
27 | activesupport (= 4.2.0)
28 | builder (~> 3.1)
29 | activerecord (4.2.0)
30 | activemodel (= 4.2.0)
31 | activesupport (= 4.2.0)
32 | arel (~> 6.0)
33 | activesupport (4.2.0)
34 | i18n (~> 0.7)
35 | json (~> 1.7, >= 1.7.7)
36 | minitest (~> 5.1)
37 | thread_safe (~> 0.3, >= 0.3.4)
38 | tzinfo (~> 1.1)
39 | addressable (2.3.6)
40 | amazon-ecs (2.3.0)
41 | nokogiri (~> 1.4)
42 | ruby-hmac (~> 0.3)
43 | arel (6.0.0)
44 | bcrypt (3.1.9)
45 | better_errors (2.1.0)
46 | coderay (>= 1.0.0)
47 | erubis (>= 2.6.6)
48 | rack (>= 0.9.0)
49 | binding_of_caller (0.7.2)
50 | debug_inspector (>= 0.0.1)
51 | bootstrap-sass (3.3.1.0)
52 | sass (~> 3.2)
53 | builder (3.2.2)
54 | byebug (3.5.1)
55 | columnize (~> 0.8)
56 | debugger-linecache (~> 1.2)
57 | slop (~> 3.6)
58 | capistrano (3.0.1)
59 | i18n
60 | rake (>= 10.0.0)
61 | sshkit (>= 0.0.23)
62 | capistrano-bundler (1.1.2)
63 | capistrano (~> 3.0)
64 | sshkit (~> 1.2)
65 | capistrano-rails (1.1.0)
66 | capistrano (>= 3.0.0)
67 | capistrano-bundler (>= 1.0.0)
68 | capistrano-rvm (0.1.2)
69 | capistrano (~> 3.0)
70 | sshkit (~> 1.2)
71 | capistrano3-puma (1.0.0)
72 | capistrano (~> 3.0)
73 | puma (>= 2.6)
74 | capybara (2.4.4)
75 | mime-types (>= 1.16)
76 | nokogiri (>= 1.3.3)
77 | rack (>= 1.0.0)
78 | rack-test (>= 0.5.4)
79 | xpath (~> 2.0)
80 | celluloid (0.16.0)
81 | timers (~> 4.0.0)
82 | childprocess (0.5.5)
83 | ffi (~> 1.0, >= 1.0.11)
84 | coderay (1.1.0)
85 | coffee-rails (4.1.0)
86 | coffee-script (>= 2.2.0)
87 | railties (>= 4.0.0, < 5.0)
88 | coffee-script (2.3.0)
89 | coffee-script-source
90 | execjs
91 | coffee-script-source (1.8.0)
92 | colorize (0.7.5)
93 | columnize (0.9.0)
94 | daemons (1.1.9)
95 | database_cleaner (1.3.0)
96 | debug_inspector (0.0.2)
97 | debugger-linecache (1.2.0)
98 | delayed_job (4.0.6)
99 | activesupport (>= 3.0, < 5.0)
100 | delayed_job_active_record (4.0.3)
101 | activerecord (>= 3.0, < 5.0)
102 | delayed_job (>= 3.0, < 4.1)
103 | delayed_job_recurring (0.3.5)
104 | delayed_job (>= 3.0)
105 | delayed_job_active_record
106 | devise (3.4.1)
107 | bcrypt (~> 3.0)
108 | orm_adapter (~> 0.1)
109 | railties (>= 3.2.6, < 5)
110 | responders
111 | thread_safe (~> 0.1)
112 | warden (~> 1.2.3)
113 | diff-lcs (1.2.5)
114 | erubis (2.7.0)
115 | execjs (2.2.2)
116 | factory_girl (4.5.0)
117 | activesupport (>= 3.0.0)
118 | factory_girl_rails (4.5.0)
119 | factory_girl (~> 4.5.0)
120 | railties (>= 3.0.0)
121 | faker (1.4.3)
122 | i18n (~> 0.5)
123 | ffi (1.9.6)
124 | formatador (0.2.5)
125 | globalid (0.3.0)
126 | activesupport (>= 4.1.0)
127 | guard (2.10.5)
128 | formatador (>= 0.2.4)
129 | listen (~> 2.7)
130 | lumberjack (~> 1.0)
131 | nenv (~> 0.1)
132 | pry (>= 0.9.12)
133 | thor (>= 0.18.1)
134 | guard-bundler (2.1.0)
135 | bundler (~> 1.0)
136 | guard (~> 2.2)
137 | guard-compat (~> 1.1)
138 | guard-compat (1.2.0)
139 | guard-rails (0.7.0)
140 | guard (~> 2.0)
141 | guard-rspec (4.5.0)
142 | guard (~> 2.1)
143 | guard-compat (~> 1.1)
144 | rspec (>= 2.99.0, < 4.0)
145 | hike (1.2.3)
146 | hitimes (1.2.2)
147 | httparty (0.13.3)
148 | json (~> 1.8)
149 | multi_xml (>= 0.5.2)
150 | hub (1.12.4)
151 | i18n (0.7.0)
152 | interception (0.5)
153 | jbuilder (2.2.6)
154 | activesupport (>= 3.0.0, < 5)
155 | multi_json (~> 1.2)
156 | jquery-rails (4.0.3)
157 | rails-dom-testing (~> 1.0)
158 | railties (>= 4.2.0)
159 | thor (>= 0.14, < 2.0)
160 | json (1.8.1)
161 | launchy (2.4.3)
162 | addressable (~> 2.3)
163 | listen (2.8.4)
164 | celluloid (>= 0.15.2)
165 | rb-fsevent (>= 0.9.3)
166 | rb-inotify (>= 0.9)
167 | loofah (2.0.1)
168 | nokogiri (>= 1.5.9)
169 | lumberjack (1.0.9)
170 | mail (2.6.3)
171 | mime-types (>= 1.16, < 3)
172 | method_source (0.8.2)
173 | mime-types (2.4.3)
174 | mini_portile (0.6.2)
175 | minitest (5.5.0)
176 | multi_json (1.10.1)
177 | multi_xml (0.5.5)
178 | mysql2 (0.3.17)
179 | nenv (0.1.1)
180 | net-scp (1.2.1)
181 | net-ssh (>= 2.6.5)
182 | net-ssh (2.9.1)
183 | nokogiri (1.6.5)
184 | mini_portile (~> 0.6.0)
185 | orm_adapter (0.5.0)
186 | pry (0.10.1)
187 | coderay (~> 1.1.0)
188 | method_source (~> 0.8.1)
189 | slop (~> 3.4)
190 | pry-rescue (1.4.1)
191 | interception (>= 0.5)
192 | pry
193 | pry-stack_explorer (0.4.9.1)
194 | binding_of_caller (>= 0.7)
195 | pry (>= 0.9.11)
196 | puma (2.11.3)
197 | rack (>= 1.1, < 2.0)
198 | quiet_assets (1.1.0)
199 | railties (>= 3.1, < 5.0)
200 | rack (1.6.0)
201 | rack-cors (0.2.9)
202 | rack-test (0.6.2)
203 | rack (>= 1.0)
204 | rails (4.2.0)
205 | actionmailer (= 4.2.0)
206 | actionpack (= 4.2.0)
207 | actionview (= 4.2.0)
208 | activejob (= 4.2.0)
209 | activemodel (= 4.2.0)
210 | activerecord (= 4.2.0)
211 | activesupport (= 4.2.0)
212 | bundler (>= 1.3.0, < 2.0)
213 | railties (= 4.2.0)
214 | sprockets-rails
215 | rails-deprecated_sanitizer (1.0.3)
216 | activesupport (>= 4.2.0.alpha)
217 | rails-dom-testing (1.0.5)
218 | activesupport (>= 4.2.0.beta, < 5.0)
219 | nokogiri (~> 1.6.0)
220 | rails-deprecated_sanitizer (>= 1.0.1)
221 | rails-html-sanitizer (1.0.1)
222 | loofah (~> 2.0)
223 | rails_layout (1.0.24)
224 | rails_param (0.0.3)
225 | railties (4.2.0)
226 | actionpack (= 4.2.0)
227 | activesupport (= 4.2.0)
228 | rake (>= 0.8.7)
229 | thor (>= 0.18.1, < 2.0)
230 | rake (10.4.2)
231 | rb-fchange (0.0.6)
232 | ffi
233 | rb-fsevent (0.9.4)
234 | rb-inotify (0.9.5)
235 | ffi (>= 0.5.0)
236 | responders (2.0.2)
237 | railties (>= 4.2.0.alpha, < 5)
238 | rspec (3.1.0)
239 | rspec-core (~> 3.1.0)
240 | rspec-expectations (~> 3.1.0)
241 | rspec-mocks (~> 3.1.0)
242 | rspec-core (3.1.7)
243 | rspec-support (~> 3.1.0)
244 | rspec-expectations (3.1.2)
245 | diff-lcs (>= 1.2.0, < 2.0)
246 | rspec-support (~> 3.1.0)
247 | rspec-mocks (3.1.3)
248 | rspec-support (~> 3.1.0)
249 | rspec-rails (3.1.0)
250 | actionpack (>= 3.0)
251 | activesupport (>= 3.0)
252 | railties (>= 3.0)
253 | rspec-core (~> 3.1.0)
254 | rspec-expectations (~> 3.1.0)
255 | rspec-mocks (~> 3.1.0)
256 | rspec-support (~> 3.1.0)
257 | rspec-support (3.1.2)
258 | ruby-hmac (0.4.0)
259 | rubyzip (1.1.6)
260 | sass (3.4.9)
261 | sass-rails (5.0.1)
262 | railties (>= 4.0.0, < 5.0)
263 | sass (~> 3.1)
264 | sprockets (>= 2.8, < 4.0)
265 | sprockets-rails (>= 2.0, < 4.0)
266 | tilt (~> 1.1)
267 | selenium-webdriver (2.44.0)
268 | childprocess (~> 0.5)
269 | multi_json (~> 1.0)
270 | rubyzip (~> 1.0)
271 | websocket (~> 1.0)
272 | sendgrid (1.2.0)
273 | json
274 | slop (3.6.0)
275 | spring (1.2.0)
276 | spring-commands-rspec (1.0.4)
277 | spring (>= 0.9.1)
278 | sprockets (2.12.3)
279 | hike (~> 1.2)
280 | multi_json (~> 1.0)
281 | rack (~> 1.0)
282 | tilt (~> 1.1, != 1.3.0)
283 | sprockets-rails (2.2.2)
284 | actionpack (>= 3.0)
285 | activesupport (>= 3.0)
286 | sprockets (>= 2.8, < 4.0)
287 | sshkit (1.6.1)
288 | colorize (>= 0.7.0)
289 | net-scp (>= 1.1.2)
290 | net-ssh (>= 2.8.0)
291 | thor (0.19.1)
292 | thread_safe (0.3.4)
293 | tilt (1.4.1)
294 | timers (4.0.1)
295 | hitimes
296 | turbolinks (2.5.3)
297 | coffee-rails
298 | tzinfo (1.2.2)
299 | thread_safe (~> 0.1)
300 | uglifier (2.6.1)
301 | execjs (>= 0.3.0)
302 | json (>= 1.8.0)
303 | warden (1.2.3)
304 | rack (>= 1.0)
305 | web-console (2.0.0)
306 | activemodel (~> 4.0)
307 | binding_of_caller (>= 0.7.2)
308 | railties (~> 4.0)
309 | sprockets-rails (>= 2.0, < 4.0)
310 | websocket (1.2.1)
311 | will_paginate (3.0.7)
312 | xpath (2.0.0)
313 | nokogiri (~> 1.3)
314 |
315 | PLATFORMS
316 | ruby
317 |
318 | DEPENDENCIES
319 | amazon-ecs
320 | better_errors
321 | binding_of_caller
322 | bootstrap-sass
323 | byebug
324 | capistrano
325 | capistrano-bundler
326 | capistrano-rails
327 | capistrano-rvm
328 | capistrano3-puma
329 | capybara
330 | coffee-rails (~> 4.1.0)
331 | daemons
332 | database_cleaner
333 | delayed_job_active_record
334 | delayed_job_recurring
335 | devise
336 | factory_girl_rails
337 | faker
338 | guard-bundler
339 | guard-rails
340 | guard-rspec
341 | httparty
342 | hub
343 | jbuilder (~> 2.0)
344 | jquery-rails
345 | launchy
346 | mysql2
347 | pry
348 | pry-rescue
349 | pry-stack_explorer
350 | puma
351 | quiet_assets
352 | rack-cors
353 | rails (= 4.2.0)
354 | rails_layout
355 | rails_param
356 | rb-fchange
357 | rb-fsevent
358 | rb-inotify
359 | rspec-mocks
360 | rspec-rails
361 | sass-rails (~> 5.0)
362 | selenium-webdriver
363 | sendgrid
364 | spring
365 | spring-commands-rspec
366 | turbolinks
367 | uglifier (>= 1.3.0)
368 | web-console (~> 2.0)
369 | will_paginate (~> 3.0.6)
370 |
--------------------------------------------------------------------------------
/config/initializers/devise.rb:
--------------------------------------------------------------------------------
1 | # Use this hook to configure devise mailer, warden hooks and so forth.
2 | # Many of these configuration options can be set straight in your model.
3 | Devise.setup do |config|
4 | # The secret key used by Devise. Devise uses this key to generate
5 | # random tokens. Changing this key will render invalid all existing
6 | # confirmation, reset password and unlock tokens in the database.
7 | # config.secret_key = 'd7d00c18b53013423d451777bc55548effc71dab8dccdd1b4b71dc79a8f58ad1fbfa29bc949c9f90e89ebd3ac23393ec854a5ed48219a1d030e74b9f6aee7ce5'
8 |
9 | # ==> Mailer Configuration
10 | # Configure the e-mail address which will be shown in Devise::Mailer,
11 | # note that it will be overwritten if you use your own mailer class
12 | # with default "from" parameter.
13 | # config.mailer_sender = 'no-reply@' + Rails.application.secrets.domain_name
14 |
15 | # Configure the class responsible to send e-mails.
16 | # config.mailer = 'Devise::Mailer'
17 |
18 | # ==> ORM configuration
19 | # Load and configure the ORM. Supports :active_record (default) and
20 | # :mongoid (bson_ext recommended) by default. Other ORMs may be
21 | # available as additional gems.
22 | require 'devise/orm/active_record'
23 | config.secret_key = '2b2f2b90a1421b47faf50d0c8e65419fcdc235cd5cfbe268173d756d9422130feef262494dd1914f0f80f9ff3e1a4bca996c73629da0066eb1e101de40e5ffaa'
24 |
25 | # ==> Configuration for any authentication mechanism
26 | # Configure which keys are used when authenticating a user. The default is
27 | # just :email. You can configure it to use [:username, :subdomain], so for
28 | # authenticating a user, both parameters are required. Remember that those
29 | # parameters are used only when authenticating and not when retrieving from
30 | # session. If you need permissions, you should implement that in a before filter.
31 | # You can also supply a hash where the value is a boolean determining whether
32 | # or not authentication should be aborted when the value is not present.
33 | # config.authentication_keys = [ :email ]
34 |
35 | # Configure parameters from the request object used for authentication. Each entry
36 | # given should be a request method and it will automatically be passed to the
37 | # find_for_authentication method and considered in your model lookup. For instance,
38 | # if you set :request_keys to [:subdomain], :subdomain will be used on authentication.
39 | # The same considerations mentioned for authentication_keys also apply to request_keys.
40 | # config.request_keys = []
41 |
42 | # Configure which authentication keys should be case-insensitive.
43 | # These keys will be downcased upon creating or modifying a user and when used
44 | # to authenticate or find a user. Default is :email.
45 | config.case_insensitive_keys = [ :email ]
46 |
47 | # Configure which authentication keys should have whitespace stripped.
48 | # These keys will have whitespace before and after removed upon creating or
49 | # modifying a user and when used to authenticate or find a user. Default is :email.
50 | config.strip_whitespace_keys = [ :email ]
51 |
52 | # Tell if authentication through request.params is enabled. True by default.
53 | # It can be set to an array that will enable params authentication only for the
54 | # given strategies, for example, `config.params_authenticatable = [:database]` will
55 | # enable it only for database (email + password) authentication.
56 | # config.params_authenticatable = true
57 |
58 | # Tell if authentication through HTTP Auth is enabled. False by default.
59 | # It can be set to an array that will enable http authentication only for the
60 | # given strategies, for example, `config.http_authenticatable = [:database]` will
61 | # enable it only for database authentication. The supported strategies are:
62 | # :database = Support basic authentication with authentication key + password
63 | # config.http_authenticatable = false
64 |
65 | # If 401 status code should be returned for AJAX requests. True by default.
66 | # config.http_authenticatable_on_xhr = true
67 |
68 | # The realm used in Http Basic Authentication. 'Application' by default.
69 | # config.http_authentication_realm = 'Application'
70 |
71 | # It will change confirmation, password recovery and other workflows
72 | # to behave the same regardless if the e-mail provided was right or wrong.
73 | # Does not affect registerable.
74 | # config.paranoid = true
75 |
76 | # By default Devise will store the user in session. You can skip storage for
77 | # particular strategies by setting this option.
78 | # Notice that if you are skipping storage for all authentication paths, you
79 | # may want to disable generating routes to Devise's sessions controller by
80 | # passing skip: :sessions to `devise_for` in your config/routes.rb
81 | config.skip_session_storage = [:http_auth]
82 |
83 | # By default, Devise cleans up the CSRF token on authentication to
84 | # avoid CSRF token fixation attacks. This means that, when using AJAX
85 | # requests for sign in and sign up, you need to get a new CSRF token
86 | # from the server. You can disable this option at your own risk.
87 | # config.clean_up_csrf_token_on_authentication = true
88 |
89 | # ==> Configuration for :database_authenticatable
90 | # For bcrypt, this is the cost for hashing the password and defaults to 10. If
91 | # using other encryptors, it sets how many times you want the password re-encrypted.
92 | #
93 | # Limiting the stretches to just one in testing will increase the performance of
94 | # your test suite dramatically. However, it is STRONGLY RECOMMENDED to not use
95 | # a value less than 10 in other environments. Note that, for bcrypt (the default
96 | # encryptor), the cost increases exponentially with the number of stretches (e.g.
97 | # a value of 20 is already extremely slow: approx. 60 seconds for 1 calculation).
98 | config.stretches = Rails.env.test? ? 1 : 10
99 |
100 | # Setup a pepper to generate the encrypted password.
101 | # config.pepper = '40a5ccb4a1444b6ffd14916675a1df26faeba189f4f50e3bd007d57e1877faccb1812dadabe70f23276791b86ea92a9de3946fbb4cddca0d2a0565c37d37f5f7'
102 |
103 | # ==> Configuration for :confirmable
104 | # A period that the user is allowed to access the website even without
105 | # confirming their account. For instance, if set to 2.days, the user will be
106 | # able to access the website for two days without confirming their account,
107 | # access will be blocked just in the third day. Default is 0.days, meaning
108 | # the user cannot access the website without confirming their account.
109 | # config.allow_unconfirmed_access_for = 2.days
110 |
111 | # A period that the user is allowed to confirm their account before their
112 | # token becomes invalid. For example, if set to 3.days, the user can confirm
113 | # their account within 3 days after the mail was sent, but on the fourth day
114 | # their account can't be confirmed with the token any more.
115 | # Default is nil, meaning there is no restriction on how long a user can take
116 | # before confirming their account.
117 | # config.confirm_within = 3.days
118 |
119 | # If true, requires any email changes to be confirmed (exactly the same way as
120 | # initial account confirmation) to be applied. Requires additional unconfirmed_email
121 | # db field (see migrations). Until confirmed, new email is stored in
122 | # unconfirmed_email column, and copied to email column on successful confirmation.
123 | config.reconfirmable = true
124 |
125 | # Defines which key will be used when confirming an account
126 | # config.confirmation_keys = [ :email ]
127 |
128 | # ==> Configuration for :rememberable
129 | # The time the user will be remembered without asking for credentials again.
130 | # config.remember_for = 2.weeks
131 |
132 | # Invalidates all the remember me tokens when the user signs out.
133 | config.expire_all_remember_me_on_sign_out = true
134 |
135 | # If true, extends the user's remember period when remembered via cookie.
136 | # config.extend_remember_period = false
137 |
138 | # Options to be passed to the created cookie. For instance, you can set
139 | # secure: true in order to force SSL only cookies.
140 | # config.rememberable_options = {}
141 |
142 | # ==> Configuration for :validatable
143 | # Range for password length.
144 | config.password_length = 8..128
145 |
146 | # Email regex used to validate email formats. It simply asserts that
147 | # one (and only one) @ exists in the given string. This is mainly
148 | # to give user feedback and not to assert the e-mail validity.
149 | # config.email_regexp = /\A[^@]+@[^@]+\z/
150 |
151 | # ==> Configuration for :timeoutable
152 | # The time you want to timeout the user session without activity. After this
153 | # time the user will be asked for credentials again. Default is 30 minutes.
154 | # config.timeout_in = 30.minutes
155 |
156 | # If true, expires auth token on session timeout.
157 | # config.expire_auth_token_on_timeout = false
158 |
159 | # ==> Configuration for :lockable
160 | # Defines which strategy will be used to lock an account.
161 | # :failed_attempts = Locks an account after a number of failed attempts to sign in.
162 | # :none = No lock strategy. You should handle locking by yourself.
163 | # config.lock_strategy = :failed_attempts
164 |
165 | # Defines which key will be used when locking and unlocking an account
166 | # config.unlock_keys = [ :email ]
167 |
168 | # Defines which strategy will be used to unlock an account.
169 | # :email = Sends an unlock link to the user email
170 | # :time = Re-enables login after a certain amount of time (see :unlock_in below)
171 | # :both = Enables both strategies
172 | # :none = No unlock strategy. You should handle unlocking by yourself.
173 | # config.unlock_strategy = :both
174 |
175 | # Number of authentication tries before locking an account if lock_strategy
176 | # is failed attempts.
177 | # config.maximum_attempts = 20
178 |
179 | # Time interval to unlock the account if :time is enabled as unlock_strategy.
180 | # config.unlock_in = 1.hour
181 |
182 | # Warn on the last attempt before the account is locked.
183 | # config.last_attempt_warning = true
184 |
185 | # ==> Configuration for :recoverable
186 | #
187 | # Defines which key will be used when recovering the password for an account
188 | # config.reset_password_keys = [ :email ]
189 |
190 | # Time interval you can reset your password with a reset password key.
191 | # Don't put a too small interval or your users won't have the time to
192 | # change their passwords.
193 | config.reset_password_within = 6.hours
194 |
195 | # ==> Configuration for :encryptable
196 | # Allow you to use another encryption algorithm besides bcrypt (default). You can use
197 | # :sha1, :sha512 or encryptors from others authentication tools as :clearance_sha1,
198 | # :authlogic_sha512 (then you should set stretches above to 20 for default behavior)
199 | # and :restful_authentication_sha1 (then you should set stretches to 10, and copy
200 | # REST_AUTH_SITE_KEY to pepper).
201 | #
202 | # Require the `devise-encryptable` gem when using anything other than bcrypt
203 | # config.encryptor = :sha512
204 |
205 | # ==> Scopes configuration
206 | # Turn scoped views on. Before rendering "sessions/new", it will first check for
207 | # "users/sessions/new". It's turned off by default because it's slower if you
208 | # are using only default views.
209 | # config.scoped_views = false
210 |
211 | # Configure the default scope given to Warden. By default it's the first
212 | # devise role declared in your routes (usually :user).
213 | # config.default_scope = :user
214 |
215 | # Set this configuration to false if you want /users/sign_out to sign out
216 | # only the current scope. By default, Devise signs out all scopes.
217 | # config.sign_out_all_scopes = true
218 |
219 | # ==> Navigation configuration
220 | # Lists the formats that should be treated as navigational. Formats like
221 | # :html, should redirect to the sign in page when the user does not have
222 | # access, but formats like :xml or :json, should return 401.
223 | #
224 | # If you have any extra navigational formats, like :iphone or :mobile, you
225 | # should add them to the navigational formats lists.
226 | #
227 | # The "*/*" below is required to match Internet Explorer requests.
228 | # config.navigational_formats = ['*/*', :html]
229 |
230 | # The default HTTP method used to sign out a resource. Default is :delete.
231 | config.sign_out_via = :delete
232 |
233 | # ==> OmniAuth
234 | # Add a new OmniAuth provider. Check the wiki for more information on setting
235 | # up on your models and hooks.
236 | # config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo'
237 |
238 | config.warden do |manager|
239 | manager.default_strategies(scope: :user).unshift :auth_by_guid
240 | end
241 |
242 | # ==> Mountable engine configurations
243 | # When using Devise inside an engine, let's call it `MyEngine`, and this engine
244 | # is mountable, there are some extra configurations to be taken into account.
245 | # The following options are available, assuming the engine is mounted as:
246 | #
247 | # mount MyEngine, at: '/my_engine'
248 | #
249 | # The router that invoked `devise_for`, in the example above, would be:
250 | # config.router_name = :my_engine
251 | #
252 | # When using omniauth, Devise cannot automatically set Omniauth path,
253 | # so you need to do it manually. For the users scope, it would be:
254 | # config.omniauth_path_prefix = '/my_engine/users/auth'
255 | end
256 |
--------------------------------------------------------------------------------
/spec/fixtures/amazon_response1.xml:
--------------------------------------------------------------------------------
1 |
2 | -
3 |
4 | B0088WEN1O
5 | http://www.amazon.com/ASICS-GEL-VENTURE-4-M-Womens-GEL-Venture%C2%AE/dp/B0088WEN1O%3FSubscriptionId%3DAKIAIQW3RNVVQNECDDIA%26tag%3Dcodethiscod02-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3DB0088WEN1O
6 |
7 |
8 |
9 | primary
10 |
11 | 285
12 | http://ecx.images-amazon.com/images/I/51GMGsRrXXL.jpg
13 | 500
14 |
15 |
16 | 91
17 | http://ecx.images-amazon.com/images/I/51GMGsRrXXL._SL160_.jpg
18 | 160
19 |
20 |
21 | 43
22 | http://ecx.images-amazon.com/images/I/51GMGsRrXXL._SL75_.jpg
23 | 75
24 |
25 |
26 | 17
27 | http://ecx.images-amazon.com/images/I/51GMGsRrXXL._SL30_.jpg
28 | 30
29 |
30 |
31 | 43
32 | http://ecx.images-amazon.com/images/I/51GMGsRrXXL._SL75_.jpg
33 | 75
34 |
35 |
36 | 63
37 | http://ecx.images-amazon.com/images/I/51GMGsRrXXL._SL110_.jpg
38 | 110
39 |
40 |
41 |
42 | swatch
43 |
44 | 285
45 | http://ecx.images-amazon.com/images/I/51GMGsRrXXL.jpg
46 | 500
47 |
48 |
49 | 91
50 | http://ecx.images-amazon.com/images/I/51GMGsRrXXL._SL160_.jpg
51 | 160
52 |
53 |
54 | 43
55 | http://ecx.images-amazon.com/images/I/51GMGsRrXXL._SL75_.jpg
56 | 75
57 |
58 |
59 | 17
60 | http://ecx.images-amazon.com/images/I/51GMGsRrXXL._SL30_.jpg
61 | 30
62 |
63 |
64 | 43
65 | http://ecx.images-amazon.com/images/I/51GMGsRrXXL._SL75_.jpg
66 | 75
67 |
68 |
69 | 63
70 | http://ecx.images-amazon.com/images/I/51GMGsRrXXL._SL110_.jpg
71 | 110
72 |
73 |
74 |
75 | variant
76 |
77 | 500
78 | http://ecx.images-amazon.com/images/I/51cdB59E-KL.jpg
79 | 366
80 |
81 |
82 | 160
83 | http://ecx.images-amazon.com/images/I/51cdB59E-KL._SL160_.jpg
84 | 117
85 |
86 |
87 | 75
88 | http://ecx.images-amazon.com/images/I/51cdB59E-KL._SL75_.jpg
89 | 55
90 |
91 |
92 | 30
93 | http://ecx.images-amazon.com/images/I/51cdB59E-KL._SL30_.jpg
94 | 22
95 |
96 |
97 | 75
98 | http://ecx.images-amazon.com/images/I/51cdB59E-KL._SL75_.jpg
99 | 55
100 |
101 |
102 | 110
103 | http://ecx.images-amazon.com/images/I/51cdB59E-KL._SL110_.jpg
104 | 81
105 |
106 |
107 |
108 | variant
109 |
110 | 197
111 | http://ecx.images-amazon.com/images/I/41aawvKR7ML.jpg
112 | 500
113 |
114 |
115 | 63
116 | http://ecx.images-amazon.com/images/I/41aawvKR7ML._SL160_.jpg
117 | 160
118 |
119 |
120 | 30
121 | http://ecx.images-amazon.com/images/I/41aawvKR7ML._SL75_.jpg
122 | 75
123 |
124 |
125 | 12
126 | http://ecx.images-amazon.com/images/I/41aawvKR7ML._SL30_.jpg
127 | 30
128 |
129 |
130 | 30
131 | http://ecx.images-amazon.com/images/I/41aawvKR7ML._SL75_.jpg
132 | 75
133 |
134 |
135 | 43
136 | http://ecx.images-amazon.com/images/I/41aawvKR7ML._SL110_.jpg
137 | 110
138 |
139 |
140 |
141 | variant
142 |
143 | 500
144 | http://ecx.images-amazon.com/images/I/51aYlZ9okcL.jpg
145 | 431
146 |
147 |
148 | 160
149 | http://ecx.images-amazon.com/images/I/51aYlZ9okcL._SL160_.jpg
150 | 138
151 |
152 |
153 | 75
154 | http://ecx.images-amazon.com/images/I/51aYlZ9okcL._SL75_.jpg
155 | 65
156 |
157 |
158 | 30
159 | http://ecx.images-amazon.com/images/I/51aYlZ9okcL._SL30_.jpg
160 | 26
161 |
162 |
163 | 75
164 | http://ecx.images-amazon.com/images/I/51aYlZ9okcL._SL75_.jpg
165 | 65
166 |
167 |
168 | 110
169 | http://ecx.images-amazon.com/images/I/51aYlZ9okcL._SL110_.jpg
170 | 95
171 |
172 |
173 |
174 | variant
175 |
176 | 225
177 | http://ecx.images-amazon.com/images/I/51ltEM%2BlKZL.jpg
178 | 500
179 |
180 |
181 | 72
182 | http://ecx.images-amazon.com/images/I/51ltEM%2BlKZL._SL160_.jpg
183 | 160
184 |
185 |
186 | 34
187 | http://ecx.images-amazon.com/images/I/51ltEM%2BlKZL._SL75_.jpg
188 | 75
189 |
190 |
191 | 14
192 | http://ecx.images-amazon.com/images/I/51ltEM%2BlKZL._SL30_.jpg
193 | 30
194 |
195 |
196 | 34
197 | http://ecx.images-amazon.com/images/I/51ltEM%2BlKZL._SL75_.jpg
198 | 75
199 |
200 |
201 | 50
202 | http://ecx.images-amazon.com/images/I/51ltEM%2BlKZL._SL110_.jpg
203 | 110
204 |
205 |
206 |
207 | variant
208 |
209 | 216
210 | http://ecx.images-amazon.com/images/I/415WWXXh-%2BL.jpg
211 | 500
212 |
213 |
214 | 69
215 | http://ecx.images-amazon.com/images/I/415WWXXh-%2BL._SL160_.jpg
216 | 160
217 |
218 |
219 | 32
220 | http://ecx.images-amazon.com/images/I/415WWXXh-%2BL._SL75_.jpg
221 | 75
222 |
223 |
224 | 13
225 | http://ecx.images-amazon.com/images/I/415WWXXh-%2BL._SL30_.jpg
226 | 30
227 |
228 |
229 | 32
230 | http://ecx.images-amazon.com/images/I/415WWXXh-%2BL._SL75_.jpg
231 | 75
232 |
233 |
234 | 48
235 | http://ecx.images-amazon.com/images/I/415WWXXh-%2BL._SL110_.jpg
236 | 110
237 |
238 |
239 |
240 | variant
241 |
242 | 175
243 | http://ecx.images-amazon.com/images/I/41MMiRAcCBL.jpg
244 | 500
245 |
246 |
247 | 56
248 | http://ecx.images-amazon.com/images/I/41MMiRAcCBL._SL160_.jpg
249 | 160
250 |
251 |
252 | 26
253 | http://ecx.images-amazon.com/images/I/41MMiRAcCBL._SL75_.jpg
254 | 75
255 |
256 |
257 | 10
258 | http://ecx.images-amazon.com/images/I/41MMiRAcCBL._SL30_.jpg
259 | 30
260 |
261 |
262 | 26
263 | http://ecx.images-amazon.com/images/I/41MMiRAcCBL._SL75_.jpg
264 | 75
265 |
266 |
267 | 38
268 | http://ecx.images-amazon.com/images/I/41MMiRAcCBL._SL110_.jpg
269 | 110
270 |
271 |
272 |
273 |
274 |
275 | Apparel
276 | ASICS
277 |
278 |
279 | ps269469
280 | GEL-VENTURE 4-M
281 |
282 |
283 | Mallard/Black/Yellow
284 | mens
285 |
286 | Removable insole
287 | Style number T346N represents Width model
288 | Style number T333N represents regular non-width medium style
289 | Width Should be determined by Style number found on the box label or on the Tongue of the shoe.
290 |
291 |
292 | 66
293 |
294 | ASICS
295 |
296 | 6000
297 | USD
298 | $60.00
299 |
300 | T333N
301 | ASICS
302 | GEL-VENTURE 4-M
303 | 1
304 |
305 | 400
306 | 1200
307 | 200
308 | 800
309 |
310 | 1
311 | T333N
312 | Shoes
313 | SHOES
314 | ASICS
315 | varies
316 | ASICS
317 | ASICS Women's GEL-Venture® 4
318 |
319 |
320 |
321 |
322 | Technical Details
323 | http://www.amazon.com/ASICS-GEL-VENTURE-4-M-Womens-GEL-Venture%C2%AE/dp/tech-data/B0088WEN1O%3FSubscriptionId%3DAKIAIQW3RNVVQNECDDIA%26tag%3Dcodethiscod02-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3DB0088WEN1O
324 |
325 |
326 | Add To Baby Registry
327 | http://www.amazon.com/gp/registry/baby/add-item.html%3Fasin.0%3DB0088WEN1O%26SubscriptionId%3DAKIAIQW3RNVVQNECDDIA%26tag%3Dcodethiscod02-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3DB0088WEN1O
328 |
329 |
330 | Add To Wedding Registry
331 | http://www.amazon.com/gp/registry/wedding/add-item.html%3Fasin.0%3DB0088WEN1O%26SubscriptionId%3DAKIAIQW3RNVVQNECDDIA%26tag%3Dcodethiscod02-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3DB0088WEN1O
332 |
333 |
334 | Add To Wishlist
335 | http://www.amazon.com/gp/registry/wishlist/add-item.html%3Fasin.0%3DB0088WEN1O%26SubscriptionId%3DAKIAIQW3RNVVQNECDDIA%26tag%3Dcodethiscod02-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3DB0088WEN1O
336 |
337 |
338 | Tell A Friend
339 | http://www.amazon.com/gp/pdp/taf/B0088WEN1O%3FSubscriptionId%3DAKIAIQW3RNVVQNECDDIA%26tag%3Dcodethiscod02-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3DB0088WEN1O
340 |
341 |
342 | All Customer Reviews
343 | http://www.amazon.com/review/product/B0088WEN1O%3FSubscriptionId%3DAKIAIQW3RNVVQNECDDIA%26tag%3Dcodethiscod02-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3DB0088WEN1O
344 |
345 |
346 | All Offers
347 | http://www.amazon.com/gp/offer-listing/B0088WEN1O%3FSubscriptionId%3DAKIAIQW3RNVVQNECDDIA%26tag%3Dcodethiscod02-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3DB0088WEN1O
348 |
349 |
350 |
351 |
352 | 285
353 | http://ecx.images-amazon.com/images/I/51GMGsRrXXL.jpg
354 | 500
355 |
356 |
357 | 91
358 | http://ecx.images-amazon.com/images/I/51GMGsRrXXL._SL160_.jpg
359 | 160
360 |
361 |
362 | 0
363 | 0
364 | 0
365 | 0
366 |
367 |
368 | 0
369 | 0
370 | 0
371 |
372 | B0088WEN1O
373 |
374 | 43
375 | http://ecx.images-amazon.com/images/I/51GMGsRrXXL._SL75_.jpg
376 | 75
377 |
378 |
379 |
380 | http://www.amazon.com/gp/redirect.html?camp=2025&creative=386001&location=http%3A%2F%2Fwww.amazon.com%2Fgp%2Fsearch%3Fkeywords%3Dshoes%26url%3Dsearch-alias%253Dshoes&linkCode=xm2&tag=codethiscod02-20&SubscriptionId=AKIAIQW3RNVVQNECDDIA
381 |
382 | True
383 |
384 | shoes
385 |
386 | Offers
387 | ItemAttributes
388 | Images
389 |
390 | Shoes
391 |
392 |
393 | 55443
394 | 554423
395 |
--------------------------------------------------------------------------------