diff --git a/.gitignore b/.gitignore index 03f3ce7..fd9e9ba 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ # Used by dotenv library to load environment variables. # .env +/.env ## Specific to RubyMotion: .dat* diff --git a/Gemfile b/Gemfile index 288bb87..a04316c 100644 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,5 @@ source 'https://rubygems.org' -ruby '2.3.1' +ruby '2.3.0' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '4.2.6' @@ -23,6 +23,16 @@ gem 'sdoc', '~> 0.4.0', group: :doc # Use ActiveModel has_secure_password # gem 'bcrypt', '~> 3.1.7' +gem 'yelp', require: 'yelp' +gem 'rspotify' +gem 'omniauth' +gem 'omniauth-spotify' +gem 'httparty' +gem 'simplecov', :require => false, :group => :test + + +gem 'omniauth-oauth2', '~> 1.3.1' + # Use Unicorn as the app server # gem 'unicorn' @@ -33,6 +43,12 @@ gem 'sdoc', '~> 0.4.0', group: :doc group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug' + gem 'dotenv-rails' + gem 'pry-rails' + gem 'minitest-vcr' + gem 'minitest-reporters' + gem 'webmock' + end group :development do @@ -42,4 +58,3 @@ group :development do # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem 'spring' end - diff --git a/Gemfile.lock b/Gemfile.lock index 6ff0f39..6d52e1f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -36,11 +36,14 @@ GEM minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) + addressable (2.4.0) + ansi (1.5.0) arel (6.0.3) binding_of_caller (0.7.2) debug_inspector (>= 0.0.1) builder (3.2.2) - byebug (8.2.5) + byebug (9.0.3) + coderay (1.1.1) coffee-rails (4.1.1) coffee-script (>= 2.2.0) railties (>= 4.0.0, < 5.1.x) @@ -49,11 +52,31 @@ GEM execjs coffee-script-source (1.10.0) concurrent-ruby (1.0.2) + crack (0.4.3) + safe_yaml (~> 1.0.0) debug_inspector (0.0.2) + docile (1.1.5) + domain_name (0.5.20160310) + unf (>= 0.0.5, < 1.0.0) + dotenv (2.1.1) + dotenv-rails (2.1.1) + dotenv (= 2.1.1) + railties (>= 4.0, < 5.1) erubis (2.7.0) execjs (2.6.0) + faraday (0.9.2) + multipart-post (>= 1.2, < 3) + faraday_middleware (0.10.0) + faraday (>= 0.7.4, < 0.10) globalid (0.3.6) activesupport (>= 4.1.0) + hashdiff (0.3.0) + hashie (3.4.4) + http-cookie (1.0.2) + domain_name (~> 0.5) + httparty (0.13.7) + json (~> 1.8) + multi_xml (>= 0.5.2) i18n (0.7.0) jbuilder (2.4.1) activesupport (>= 3.0.0, < 5.1) @@ -63,19 +86,53 @@ GEM railties (>= 4.2.0) thor (>= 0.14, < 2.0) json (1.8.3) + jwt (1.5.1) loofah (2.0.3) nokogiri (>= 1.5.9) mail (2.6.4) mime-types (>= 1.16, < 4) - mime-types (3.0) - mime-types-data (~> 3.2015) - mime-types-data (3.2016.0221) + method_source (0.8.2) + mime-types (2.99.1) mini_portile2 (2.0.0) - minitest (5.8.4) + minispec-metadata (2.0.0) + minitest + minitest (5.9.0) + minitest-reporters (1.1.9) + ansi + builder + minitest (>= 5.0) + ruby-progressbar + minitest-vcr (1.4.0) + minispec-metadata (~> 2.0) + minitest (>= 4.7.5) + vcr (>= 2.9) multi_json (1.12.0) + multi_xml (0.5.5) + multipart-post (2.0.0) + netrc (0.11.0) nokogiri (1.6.7.2) mini_portile2 (~> 2.0.0.rc2) + oauth2 (1.1.0) + faraday (>= 0.8, < 0.10) + jwt (~> 1.0, < 1.5.2) + multi_json (~> 1.3) + multi_xml (~> 0.5) + rack (>= 1.2, < 3) + omniauth (1.3.1) + hashie (>= 1.2, < 4) + rack (>= 1.0, < 3) + omniauth-oauth2 (1.3.1) + oauth2 (~> 1.0) + omniauth (~> 1.2) + omniauth-spotify (0.0.9) + omniauth-oauth2 (~> 1.1) pg (0.18.4) + pry (0.10.3) + coderay (~> 1.1.0) + method_source (~> 0.8.1) + slop (~> 3.4) + pry-rails (0.3.4) + pry (>= 0.9.10) rack (1.6.4) rack-test (0.6.3) rack (>= 1.0) @@ -106,6 +163,15 @@ GEM rake (11.1.2) rdoc (4.2.2) json (~> 1.4) + rest-client (1.8.0) + http-cookie (>= 1.0.2, < 2.0) + mime-types (>= 1.16, < 3.0) + netrc (~> 0.7) + rspotify (1.18.0) + omniauth-oauth2 (~> 1.3.1) + rest-client (~> 1.7) + ruby-progressbar (1.8.1) + safe_yaml (1.0.4) sass (3.4.22) sass-rails (5.0.4) railties (>= 4.0.0, < 5.0) @@ -116,6 +182,13 @@ GEM sdoc (0.4.1) json (~> 1.7, >= 1.7.7) rdoc (~> 4.0) + simple_oauth (0.3.1) + simplecov (0.11.2) + docile (~> 1.1.0) + json (~> 1.8) + simplecov-html (~> 0.10.0) + simplecov-html (0.10.0) + slop (3.6.0) spring (1.7.1) sprockets (3.6.0) concurrent-ruby (~> 1.0) @@ -126,16 +199,28 @@ GEM sprockets (>= 3.0.0) thor (0.19.1) thread_safe (0.3.5) - tilt (2.0.2) + tilt (2.0.4) tzinfo (1.2.2) thread_safe (~> 0.1) uglifier (3.0.0) execjs (>= 0.3.0, < 3) + unf (0.1.4) + unf_ext + unf_ext (0.0.7.2) + vcr (3.0.3) web-console (2.3.0) activemodel (>= 4.0) binding_of_caller (>= 0.7.2) railties (>= 4.0) sprockets-rails (>= 2.0, < 4.0) + webmock (2.0.3) + addressable (>= 2.3.6) + crack (>= 0.3.2) + hashdiff + yelp (2.1.2) + faraday (~> 0.8, >= 0.8.0) + faraday_middleware (~> 0.8, >= 0.8.0) + simple_oauth (~> 0.3.1) PLATFORMS ruby @@ -143,15 +228,30 @@ PLATFORMS DEPENDENCIES byebug coffee-rails (~> 4.1.0) + dotenv-rails + httparty jbuilder (~> 2.0) jquery-rails + minitest-reporters + minitest-vcr + omniauth + omniauth-oauth2 (~> 1.3.1) + omniauth-spotify pg (~> 0.15) + pry-rails rails (= 4.2.6) + rspotify sass-rails (~> 5.0) sdoc (~> 0.4.0) + simplecov spring uglifier (>= 1.3.0) web-console (~> 2.0) + webmock + yelp + +RUBY VERSION + ruby 2.3.0p0 BUNDLED WITH - 1.12.3 + 1.12.4 diff --git a/app/assets/javascripts/sessions.coffee b/app/assets/javascripts/sessions.coffee new file mode 100644 index 0000000..24f83d1 --- /dev/null +++ b/app/assets/javascripts/sessions.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/javascripts/suggestions.coffee b/app/assets/javascripts/suggestions.coffee new file mode 100644 index 0000000..24f83d1 --- /dev/null +++ b/app/assets/javascripts/suggestions.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index f9cd5b3..ee35ae5 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -1,15 +1,19 @@ -/* - * This is a manifest file that'll be compiled into application.css, which will include all the files - * listed below. - * - * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, - * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. - * - * You're free to add application-wide styles to this file and they'll appear at the bottom of the - * compiled file so the styles you add here take precedence over styles defined in any styles - * defined in the other CSS/SCSS files in this directory. It is generally better to create a new - * file per style scope. - * - *= require_tree . - *= require_self - */ + +.avatar-container { + float: right; + padding: 1em; +} + +.avatar-image { + height: 25%; +} + +.suggestion-table { + padding: 15px; +} + +th, td { + padding: 15px; + text-align: center; + border-bottom: 1px solid #ddd; +} diff --git a/app/assets/stylesheets/sessions.scss b/app/assets/stylesheets/sessions.scss new file mode 100644 index 0000000..7bef9cf --- /dev/null +++ b/app/assets/stylesheets/sessions.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the sessions controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/suggestions.scss b/app/assets/stylesheets/suggestions.scss new file mode 100644 index 0000000..c0d3b8d --- /dev/null +++ b/app/assets/stylesheets/suggestions.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the suggestions controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index d83690e..aee5e4a 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -2,4 +2,12 @@ class ApplicationController < ActionController::Base # Prevent CSRF attacks by raising an exception. # For APIs, you may want to use :null_session instead. protect_from_forgery with: :exception + + helper_method :current_user + + + def current_user + @user ||= User.find_by(id: session[:user_id]) + end + end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb new file mode 100644 index 0000000..6b7fc3c --- /dev/null +++ b/app/controllers/sessions_controller.rb @@ -0,0 +1,26 @@ +class SessionsController < ApplicationController + + def new #why not initialize? + #shows a view with OAuth sign-in link + end + + def create + #accepts OAuth information from Spotify, finds or creates a User account, and sets user_id in session + auth_hash = request.env['omniauth.auth'] #request is a variable that you get + @user = User.find_or_create_from_omniauth(auth_hash) + # user = User.log_in(params[:xemail], params[:password]) + if @user + session[:user_id] = @user.id + redirect_to root_path + else + redirect_to root_path + end + end + + def destroy + #deletes user_id from session + session.delete :user_id + redirect_to root_path + end + +end diff --git a/app/controllers/suggestions_controller.rb b/app/controllers/suggestions_controller.rb new file mode 100644 index 0000000..a792904 --- /dev/null +++ b/app/controllers/suggestions_controller.rb @@ -0,0 +1,116 @@ +require_relative '../../lib/TunesTakeoutWrapper.rb' +require 'yelp' +require 'httparty' + +class SuggestionsController < ApplicationController +#skip before action + + def index + #shows top 20 suggestions, ranked by total number of favorites + suggestion_ids = TunesTakeoutWrapper.top["suggestions"] #this should return array of sugg ids + + @pairings = [] + suggestion_ids.each do |suggestion_id| + + suggestion_object = TunesTakeoutWrapper.find(suggestion_id) + + suggestion = suggestion_object["suggestion"] + + yelp_id = suggestion["food_id"] + spotify_id = suggestion["music_id"] + spotify_type = suggestion["music_type"] + + if current_user + favorite = is_favorite?(suggestion["id"]) + end + + + array = [] + array << Food.find(yelp_id) #first thing in array is yelp + array << Music.find(spotify_id, spotify_type) #second thing is music + array << suggestion["id"] + if favorite + array << favorite + end + + @pairings << array + end + end + + + def search_by_term + #Returns a hash from the TunesTakeoutWrapper + end + + def display_results + results = TunesTakeoutWrapper.search(params[:term]) + suggestions = results["suggestions"] + + @pairings = [] + suggestions.each do |suggestion| + + yelp_id = suggestion["food_id"] + spotify_id = suggestion["music_id"] + spotify_type = suggestion["music_type"] + + favorite = is_favorite?(suggestion["id"]) + + array = [] + array << Food.find(yelp_id) #first thing in array is yelp + array << Music.find(spotify_id, spotify_type) #second thing is music + array << suggestion["id"] + array << favorite + + @pairings << array + + end + + end + + def favorite + #adds a suggestion into the favorite list for the signed-in User. This requires interaction with the Tunes & Takeout API. + TunesTakeoutWrapper.add_favorite(current_user.uid, params[:suggestion_id]) + redirect_to root_path + end + + def unfavorite + #removes a suggestion from the favorite list for the signed-in User. This requires interaction with the Tunes & Takeout API + TunesTakeoutWrapper.remove_favorite(current_user.uid, params[:suggestion_id]) + redirect_to root_path + end + + def is_favorite?(suggestion_id) + favorites_response = TunesTakeoutWrapper.favorites(current_user.uid) + user_favorites = favorites_response["suggestions"] #an array + return user_favorites.include? suggestion_id + end + + def favorites + favorites_response = TunesTakeoutWrapper.favorites(current_user.uid) + suggestion_ids = favorites_response["suggestions"] #this is array of suggestion_ids + + @pairings = [] + suggestion_ids.each do |suggestion_id| + + suggestion_object = TunesTakeoutWrapper.find(suggestion_id) + suggestion = suggestion_object["suggestion"] + + yelp_id = suggestion["food_id"] + spotify_id = suggestion["music_id"] + spotify_type = suggestion["music_type"] + + favorite = is_favorite?(suggestion["id"]) + + array = [] + array << Food.find(yelp_id) #first thing in array is yelp + array << Music.find(spotify_id, spotify_type) #second thing is music + array << suggestion["id"] + array << favorite + + @pairings << array + end + + + end + +end diff --git a/app/helpers/sessions_helper.rb b/app/helpers/sessions_helper.rb new file mode 100644 index 0000000..309f8b2 --- /dev/null +++ b/app/helpers/sessions_helper.rb @@ -0,0 +1,2 @@ +module SessionsHelper +end diff --git a/app/helpers/suggestions_helper.rb b/app/helpers/suggestions_helper.rb new file mode 100644 index 0000000..0e358dd --- /dev/null +++ b/app/helpers/suggestions_helper.rb @@ -0,0 +1,2 @@ +module SuggestionsHelper +end diff --git a/app/models/food.rb b/app/models/food.rb new file mode 100644 index 0000000..8d772f7 --- /dev/null +++ b/app/models/food.rb @@ -0,0 +1,32 @@ +# business_id string Yelp-specific ID, which can be used to make calls to the Yelp Business API to retrieve complete details +# name string Name of the business +# url string Yelp URL for this business +# image_url string URL of the photo to display for this business +# phone string Phone number for the business +# rating decimal number Average rating for this business based on Yelp reviews + +require 'httparty' + +class Food + BASE_URL = "https://api.yelp.com/v2" + attr_reader :business_id, :name, :url, :image_url, :phone, :rating + + def initialize(data) + @business_id = data.business.id + @name = data.business.name + @url = data.business.url + @image_url = data.business.image_url + @phone = data.business.phone + @rating = data.business.rating + end + + def self.find(id) + # https://api.yelp.com/v2/search?term=food + #find a business with the passed term +++ NOT TERM, BUT business_id? + #data = HTTParty.get(BASE_URL + "/search?term=#{term}").parsed_response + data = Yelp.client.business(id) + #return an array of instances of business for that term + self.new(data) + end + +end diff --git a/app/models/music.rb b/app/models/music.rb new file mode 100644 index 0000000..517c8c2 --- /dev/null +++ b/app/models/music.rb @@ -0,0 +1,42 @@ +# attribute data type description +# item_id string Spotify-specific ID, which can be used in conjunction with the type to make calls to the Spotify API +# type string Which type of resource this item is. Can be: artist, album, track, playlist +# name string Name of the item +# url string URL for opening this item in a browser-based Spotify player +# image_url string URL of the photo to display for this item + + +require 'rspotify' + +class Music + BASE_URL = "http://" + attr_reader :item_id, :type, :name, :url, :image_url + + def initialize(data) + @item_id = data.id + @type = data.type + @name = data.name + @url = data.external_urls["spotify"] + if data.type == "album" || data.type == "artist" + @image_url = data.images.last["url"] if !data.images.empty? + end + @image_url = data.album.images.last["url"] if data.type == "track" #first is widest image + end + + def self.find(id, type) + if type == "album" + data = RSpotify::Album.find(id) + self.new(data) + elsif type == "track" + data = RSpotify::Track.find(id) + self.new(data) + elsif type == "artist" + data = RSpotify::Artist.find(id) + self.new(data) + end + + end + +end + +# @music.external_urls["spotify"] <= to listen to track! diff --git a/app/models/user.rb b/app/models/user.rb new file mode 100644 index 0000000..6f200aa --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,51 @@ +class User < ActiveRecord::Base + #User: A signed-in user account, created via OmniAuth and connected to a Spotify account. + # Validations: + # provider must be a string, must be present, and must equal spotify + # uid must be a string, and must be present + # name, if present, must be a string + validates :uid, :provider, presence: true + validate :provider_must_be_spotify + validate :name_cant_be_empty_string + + def provider_must_be_spotify + if provider != "spotify" + errors.add(:provider, "provider must be spotify") + end + end + + def name_cant_be_empty_string + if name == "" + errors.add(:name, "name can't be empty string") + end + end + + + def self.find_or_create_from_omniauth(auth_hash) + # Find or create a user + #user = User.where(uid: auth_hash["uid"]).where(provider: auth_hash["provider"]).first + user = self.find_by(uid: auth_hash["uid"], provider: auth_hash["provider"]) + # user = //something else here// + if !user.nil? + # return user that was found + return user + else + # no user found, do something here + user = User.new + user.uid = auth_hash["uid"] + user.provider = auth_hash["provider"] + user.name = auth_hash["info"]["name"] + user.image = auth_hash["info"]["image"] + user.url = auth_hash["info"]["urls"]["spotify"] + if user.save + return user + else + return nil + end + end + end + + + + +end diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 509d1a2..ccc770c 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -3,12 +3,37 @@