diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..1855c69 Binary files /dev/null and b/.DS_Store differ diff --git a/.gitignore b/.gitignore index ba5d71e..7752e1c 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ !/log/.keep /tmp /.env +/coverage/* +/.yml diff --git a/Gemfile b/Gemfile index f10f791..fbc2477 100644 --- a/Gemfile +++ b/Gemfile @@ -1,10 +1,11 @@ source 'https://rubygems.org' + ruby '2.2.3' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '4.2.5' # Use sqlite3 as the database for Active Record -gem 'sqlite3' + # Use SCSS for stylesheets gem 'sass-rails', '~> 5.0' # Use Uglifier as compressor for JavaScript assets @@ -27,7 +28,9 @@ gem "omniauth" gem "omniauth-github" gem "omniauth-twitter" gem "omniauth-vimeo" +gem 'omniauth-oauth2', '~> 1.3.1' gem 'httparty' +gem 'twitter' gem 'bootstrap-sass', '~> 3.3.5' @@ -35,6 +38,7 @@ gem 'bootstrap-sass', '~> 3.3.5' + # Use ActiveModel has_secure_password # gem 'bcrypt', '~> 3.1.7' @@ -48,6 +52,10 @@ group :production do gem "pg" end +group :test do + gem 'webmock', '1.8.0' +end + group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug' @@ -55,14 +63,18 @@ group :development, :test do gem 'dotenv-rails' gem 'simplecov' gem 'pry' + gem 'factory_girl_rails' + gem 'vcr' end group :development do # Access an IRB console on exception pages or by using <%= console %> in views + gem 'sqlite3' gem 'web-console', '~> 2.0' gem "better_errors" gem "binding_of_caller" # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem 'spring' + gem 'pry-rails' end diff --git a/Gemfile.lock b/Gemfile.lock index fa2698e..944cb0a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -36,6 +36,7 @@ GEM minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) + addressable (2.4.0) arel (6.0.3) autoprefixer-rails (6.2.3) execjs @@ -49,6 +50,7 @@ GEM bootstrap-sass (3.3.6) autoprefixer-rails (>= 5.2.1) sass (>= 3.3.4) + buftok (0.2.0) builder (3.2.2) byebug (8.2.1) coderay (1.1.0) @@ -60,20 +62,39 @@ GEM execjs coffee-script-source (1.10.0) concurrent-ruby (1.0.0) + crack (0.4.3) + safe_yaml (~> 1.0.0) debug_inspector (0.0.2) diff-lcs (1.2.5) docile (1.1.5) + domain_name (0.5.25) + unf (>= 0.0.5, < 1.0.0) dotenv (2.0.2) dotenv-rails (2.0.2) dotenv (= 2.0.2) railties (~> 4.0) + equalizer (0.0.10) erubis (2.7.0) execjs (2.6.0) + factory_girl (4.5.0) + activesupport (>= 3.0.0) + factory_girl_rails (4.5.0) + factory_girl (~> 4.5.0) + railties (>= 3.0.0) faraday (0.9.2) multipart-post (>= 1.2, < 3) globalid (0.3.6) activesupport (>= 4.1.0) hashie (3.4.3) + http (0.9.8) + addressable (~> 2.3) + http-cookie (~> 1.0) + http-form_data (~> 1.0.1) + http_parser.rb (~> 0.6.0) + http-cookie (1.0.2) + domain_name (~> 0.5) + http-form_data (1.0.1) + http_parser.rb (0.6.0) httparty (0.13.7) json (~> 1.8) multi_xml (>= 0.5.2) @@ -91,6 +112,8 @@ GEM nokogiri (>= 1.5.9) mail (2.6.3) mime-types (>= 1.16, < 3) + memoizable (0.4.2) + thread_safe (~> 0.3, >= 0.3.1) method_source (0.8.2) mime-types (2.99) mini_portile2 (2.0.0) @@ -98,6 +121,7 @@ GEM multi_json (1.11.2) multi_xml (0.5.5) multipart-post (2.0.0) + naught (1.1.0) nokogiri (1.6.7.1) mini_portile2 (~> 2.0.0.rc2) oauth (0.4.7) @@ -116,7 +140,7 @@ GEM omniauth-oauth (1.1.0) oauth omniauth (~> 1.0) - omniauth-oauth2 (1.4.0) + omniauth-oauth2 (1.3.1) oauth2 (~> 1.0) omniauth (~> 1.2) omniauth-twitter (1.2.1) @@ -129,6 +153,8 @@ GEM 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) @@ -176,6 +202,7 @@ GEM rspec-mocks (~> 3.4.0) rspec-support (~> 3.4.0) rspec-support (3.4.1) + safe_yaml (1.0.4) sass (3.4.20) sass-rails (5.0.4) railties (>= 4.0.0, < 5.0) @@ -186,6 +213,7 @@ GEM sdoc (0.4.1) json (~> 1.7, >= 1.7.7) rdoc (~> 4.0) + simple_oauth (0.3.1) simplecov (0.11.1) docile (~> 1.1.0) json (~> 1.8) @@ -206,16 +234,34 @@ GEM tilt (2.0.2) turbolinks (2.5.3) coffee-rails + twitter (5.15.0) + addressable (~> 2.3) + buftok (~> 0.2.0) + equalizer (= 0.0.10) + faraday (~> 0.9.0) + http (>= 0.4, < 0.10) + http_parser.rb (~> 0.6.0) + json (~> 1.8) + memoizable (~> 0.4.0) + naught (~> 1.0) + simple_oauth (~> 0.3.0) tzinfo (1.2.2) thread_safe (~> 0.1) uglifier (2.7.2) execjs (>= 0.3.0) json (>= 1.8.0) + unf (0.1.4) + unf_ext + unf_ext (0.0.7.1) + vcr (3.0.1) web-console (2.2.1) activemodel (>= 4.0) binding_of_caller (>= 0.7.2) railties (>= 4.0) sprockets-rails (>= 2.0, < 4.0) + webmock (1.8.0) + addressable (>= 2.2.7) + crack (>= 0.1.7) PLATFORMS ruby @@ -227,15 +273,18 @@ DEPENDENCIES byebug coffee-rails (~> 4.1.0) dotenv-rails + factory_girl_rails httparty jbuilder (~> 2.0) jquery-rails omniauth omniauth-github + omniauth-oauth2 (~> 1.3.1) omniauth-twitter omniauth-vimeo pg pry + pry-rails rails (= 4.2.5) rspec-rails sass-rails (~> 5.0) @@ -244,8 +293,11 @@ DEPENDENCIES spring sqlite3 turbolinks + twitter uglifier (>= 1.3.0) + vcr web-console (~> 2.0) + webmock (= 1.8.0) BUNDLED WITH 1.11.2 diff --git a/app/Procfile b/app/Procfile new file mode 100644 index 0000000..a7662ff --- /dev/null +++ b/app/Procfile @@ -0,0 +1 @@ + web: bundle exec rackup -p $PORT diff --git a/app/assets/images/favicon.ico b/app/assets/images/favicon.ico new file mode 100644 index 0000000..380fc6a Binary files /dev/null and b/app/assets/images/favicon.ico differ diff --git a/app/assets/images/twitter_default_image.png b/app/assets/images/twitter_default_image.png new file mode 100644 index 0000000..9d802e1 Binary files /dev/null and b/app/assets/images/twitter_default_image.png differ diff --git a/app/assets/images/vimeo_default_image.png b/app/assets/images/vimeo_default_image.png new file mode 100644 index 0000000..3104b89 Binary files /dev/null and b/app/assets/images/vimeo_default_image.png differ diff --git a/app/assets/images/vimeo_default_two.png b/app/assets/images/vimeo_default_two.png new file mode 100644 index 0000000..bacac51 Binary files /dev/null and b/app/assets/images/vimeo_default_two.png differ diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css deleted file mode 100644 index f9cd5b3..0000000 --- a/app/assets/stylesheets/application.css +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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 - */ diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss new file mode 100644 index 0000000..63547ef --- /dev/null +++ b/app/assets/stylesheets/application.scss @@ -0,0 +1,116 @@ +@import "bootstrap-sprockets"; +@import "bootstrap"; +@import 'bootstrap/theme'; + + +$light-grey: #707070; +$med-grey: #454545; + + +body { + font-family: 'Cardo', serif; + text-align: center; + color: $med-grey; +} + +.flash { + font-family: 'Cardo', serif; + font-size: 14px; + text-align: center; + margin-top: 5px; + margin-bottom: 5px; + padding-top: 8px; + padding-bottom: 8px; + background-color: $med-grey; + color: #FFFFFF; +} + +h1 { + font-family: 'Oswald', serif; + color: $med-grey; +} + +.navbar-default { + background-color: #76CCAF; + margin-top: 7px; +} + +.navbar-right { + margin-right: 30px; + margin-top: 7px; +} + +.navbar-left { + margin-left: 20px; + margin-top: 12px; +} + +.navbar-links { + font-size: 18px; + margin-top: 17px; +} + +#navbar-text-right { + font-size: 18px; + margin-top: 5px; + color: $light-grey; +} + +#navbar-text-left { + font-size: 18px; + color: $light-grey; +} + +.search-button { + margin-left: 4px; + margin-bottom: 13px; +} + +.login-links { + font-size: 125px; +} + +.login-link-right { + margin-left: 15px; +} + +.login-link-left { + margin-right: 15px; + margin-left: 15px; +} + +.btn-default { + font-family: 'Oswald', serif; + border: 1px $med-grey solid; + margin-top: 8px; +} + +.creator_name { + margin-top: 5px; +} + +.creator-image { + height: 75px; +} + +.creator-col { + height: 200px; +} + +.embedded-video { + margin: 30px 0; +} + +.embedded-tweet { + float:left; + text-align:center; +} + +a { + color: $med-grey; +} + +a:hover { + color: $light-grey; + text-decoration: none; +} \ No newline at end of file diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index d83690e..a124a57 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -2,4 +2,19 @@ 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 + before_action :require_login + before_action :current_user + + def current_user + @current_user ||= User.find(session[:user_id]) if session[:user_id] + rescue ActiveRecord::RecordNotFound + end + + def require_login + unless current_user + flash[:error] = "Login to begin curating." + redirect_to new_session_path + end + end end diff --git a/app/controllers/creators_controller.rb b/app/controllers/creators_controller.rb index aa6a33a..ea974ce 100644 --- a/app/controllers/creators_controller.rb +++ b/app/controllers/creators_controller.rb @@ -1,2 +1,31 @@ class CreatorsController < ApplicationController + + def index + if @current_user.creators != [] + @creators = @current_user.creators + else + flash.now[:notice] = "Try following some people first!" + end + end + + def follow + creator = Creator.find_or_create(params) + if creator.nil? + flash[:error] = "Failed to follow" + else + if !@current_user.creators.include?(creator) + creator.users << @current_user + flash[:notice] = "You're now following #{creator.name}." + elsif @current_user.creators.include?(creator) + flash[:notice] = "You're already following #{creator.name}" + end + end + redirect_to :back + end + + def unfollow + creator = Creator.find_or_create(params) + @current_user.creators.delete(creator) + redirect_to :back + end end diff --git a/app/controllers/searches_controller.rb b/app/controllers/searches_controller.rb index 14ddbfb..4ed7d92 100644 --- a/app/controllers/searches_controller.rb +++ b/app/controllers/searches_controller.rb @@ -1,2 +1,47 @@ +require 'json' class SearchesController < ApplicationController + + + def search + @current_user = @current_user + if params[:provider].nil? + flash.now[:notice] = "Please select whether you want to search Twitter or Vimeo." + render :search + else + if params[:search].nil? + flash.now[:notice] = "Please select a search term, yo!" + render :search + else + search_term = params[:search] + @provider = params[:provider] + if @provider == "vimeo" + results = vimeo_search_api_call(search_term) + if results["total"] == 0 + flash.now[:error] = "No results matched your search." + else + @results = results["data"] + end + elsif @provider == "twitter" + @results = twitter_search_api_call(search_term) + if @results.length == 0 + flash.now[:error] = "No results matched your search." + end + end + end + end + end + + private + + def vimeo_search_api_call(search_term) + vimeo_access_token = ENV["VIMEO_ACCESS_TOKEN"] + results = HTTParty.get("https://api.vimeo.com/users?page=1&per_page=25&query=#{search_term}&fields=uri,name,pictures", + headers: {"Authorization" => "bearer #{vimeo_access_token}", 'Accept' => 'application/json' }, format: :json).parsed_response + return results + end + + def twitter_search_api_call(search_term) + $twitter.user_search("#{search_term}").take(25) + end + end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 16d11b5..ba010b6 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -1,2 +1,27 @@ class SessionsController < ApplicationController + before_action :require_login, except: [:new, :create] + skip_before_filter :verify_authenticity_token, only: [:create] + + def new + end + + def create + auth_hash = request.env['omniauth.auth'] + if auth_hash["uid"] + @user = User.find_or_create_from_omniauth(auth_hash) + if @user + session[:user_id] = @user.id + redirect_to feed_path, notice: "Welcome to CurateTV. Happy Curating!" + else + redirect_to root_path, notice: "Failed to save the user." + end + else + redirect_to root_path, notice: "Failed to authenticate." + end + end + + def destroy + session[:user_id] = nil + redirect_to new_session_path, notice: "You've logged out. See you next time." + end end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 3e74dea..fbc7804 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,2 +1,9 @@ class UsersController < ApplicationController + + def show + videos = @current_user.videos.to_ary.sort_by{|video| video.posted_at}.reverse![0..8] + tweets = @current_user.tweets.to_ary.sort_by{|tweet| tweet.posted_at}.reverse![0..8] + @media = [tweets, videos].flatten.sort_by{|media| media.posted_at}.reverse! + end + end diff --git a/app/models/category.rb b/app/models/category.rb new file mode 100644 index 0000000..4a75aad --- /dev/null +++ b/app/models/category.rb @@ -0,0 +1,4 @@ +class Category < ActiveRecord::Base + belongs_to :user + belongs_to :creator +end diff --git a/app/models/creator.rb b/app/models/creator.rb index ba4f9ee..59392c1 100644 --- a/app/models/creator.rb +++ b/app/models/creator.rb @@ -1,2 +1,67 @@ class Creator < ActiveRecord::Base + validates_presence_of :name, :uid, :provider + validates_uniqueness_of :uid + has_many :categories + has_many :users, through: :categories + has_many :videos + has_many :tweets + + def self.find_or_create(params) + creator = self.find_by(uid: params["uid"], provider: params["provider"]) + if !creator.nil? + return creator + else + Creator.transaction do + creator = Creator.new( + name: params["name"], + provider: params["provider"], + uid: params["uid"] + ) + if creator.provider == "vimeo" + if !params["profile_pic"].nil? + creator.profile_pic = params["profile_pic"]["sizes"][2]["link"] + else + creator.profile_pic = "vimeo_default_image.png" + end + creator.save + video_data_array = creator.get_videos_info + Video.create_videos_for_creator_from_hashes(video_data_array, creator) + elsif creator.provider == "twitter" + if !params["profile_pic"].nil? + creator.profile_pic = params["profile_pic"] + else + creator.profile_pic = "twitter_default_image.png" + end + creator.save + Tweet.find_or_create_tweets(creator) + end + end + end + if creator.save + return creator + else + return nil + end + end + + def get_videos_info + vimeo_access_token = ENV["VIMEO_ACCESS_TOKEN"] + videos = HTTParty.get("https://api.vimeo.com/users/#{self.uid}/videos", + headers: {"Authorization" => "bearer #{vimeo_access_token}", 'Accept' => 'application/json' }, format: :json).parsed_response + if videos["error"].nil? + if videos["data"].nil? + return nil + else + return videos["data"] + end + elsif !videos["error"].nil? + raise ArgumentError + end + end + + + def get_tweets + $twitter.user_timeline(self.name).take(25) + end + end diff --git a/app/models/tweet.rb b/app/models/tweet.rb index a8fa51a..ce6a11f 100644 --- a/app/models/tweet.rb +++ b/app/models/tweet.rb @@ -1,2 +1,16 @@ class Tweet < ActiveRecord::Base + + def self.find_or_create_tweets(creator) + all_tweets = creator.get_tweets + all_tweets.each do |tweet| + existing_tweet = self.find_by(uid: tweet.id) + if existing_tweet.nil? + tweet = Tweet.create( + creator_id: creator.id, + posted_at: tweet.created_at, + uid: tweet.id + ) + end + end + end end diff --git a/app/models/user.rb b/app/models/user.rb index 4a57cf0..1422d97 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,2 +1,47 @@ class User < ActiveRecord::Base + has_many :categories + has_many :creators, through: :categories + has_many :tweets, through: :creators + has_many :videos, through: :creators + validates_presence_of :name, :uid, :provider + validates_uniqueness_of :uid + + def self.find_or_create_from_omniauth(auth_hash) + user = self.find_by(uid: auth_hash["uid"], provider: auth_hash["provider"]) + if !user.nil? + # User found continue on with your life + return user + else + # Create a new user + user = User.new + user.uid = auth_hash["uid"] + user.provider = auth_hash["provider"] + user.name = auth_hash["info"]["name"] + user.email = auth_hash["info"]["email"] + if user.provider == "twitter" + user.avatar_url = auth_hash["info"]["image"] + elsif user.provider == "vimeo" + user.avatar_url = auth_hash["info"]["pictures"][2]["link"] if auth_hash["info"]["pictures"] + end + if user.save + return user + else + return nil + end + end + end + + # this two methods will run for every creator we search on the search page view + # it will choose which button to show - "Follow" or "Unfollow" + + def twitter_follow?(result) + creator = Creator.find_by(uid: result.id, provider: "twitter") + return true if self.creators.include?(creator) + end + + def vimeo_follow?(result) + creator = Creator.find_by(uid: result["uri"].gsub(/[^0-9]/, ""), provider: "vimeo") + return true if self.creators.include?(creator) + end + end diff --git a/app/models/video.rb b/app/models/video.rb index 0593c0c..45dd481 100644 --- a/app/models/video.rb +++ b/app/models/video.rb @@ -1,2 +1,26 @@ class Video < ActiveRecord::Base + belongs_to :creator + validates_presence_of :uri, :embed, :vimeo_id, :posted_at + validates_uniqueness_of :uri + + def self.create_videos_for_creator_from_hashes(video_data_array, creator) + if !video_data_array.nil? + video_data_array.each do |video_hash| + video_id = video_hash["uri"].gsub(/[^0-9]/, "") + vid = Video.new ({ + uri: "#{video_hash["uri"]}", + name: "#{video_hash["name"]}", + description: "#{video_hash["description"]}", + embed: "https:\/\/player.vimeo.com\/video\/#{video_id}", + posted_at: "#{video_hash["created_time"]}", + vimeo_id: "#{video_id}" + }) + vid.creator_id = creator.id + vid.save + end + else + return nil + end + end + end diff --git a/app/views/application/_navbar.html.erb b/app/views/application/_navbar.html.erb new file mode 100644 index 0000000..48e41b4 --- /dev/null +++ b/app/views/application/_navbar.html.erb @@ -0,0 +1,23 @@ +<% if @current_user %> +