diff --git a/Gemfile b/Gemfile index 4cb6171e..7036bf49 100644 --- a/Gemfile +++ b/Gemfile @@ -183,9 +183,10 @@ gem 'dotenv-rails' gem 'faker' # for seeds gem 'factory_girl_rails' - # for integrating Stripe payment gateway gem 'stripe' +#For embedding tweets +gem 'twitter' # Provides Sprockets implementation for Rails Asset Pipeline gem 'sprockets-rails' diff --git a/Gemfile.lock b/Gemfile.lock index 145a969d..a3bc8e23 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -82,6 +82,7 @@ GEM bootstrap3-datetimepicker-rails (3.0.3) momentjs-rails (>= 2.8.1) browser (0.6.0) + buftok (0.2.0) builder (3.2.2) byebug (3.1.2) columnize (~> 0.8) @@ -167,6 +168,7 @@ GEM dotenv-rails (2.1.1) dotenv (= 2.1.1) railties (>= 4.0, < 5.1) + equalizer (0.0.10) erubis (2.7.0) execjs (2.6.0) factory_girl (4.5.0) @@ -214,8 +216,15 @@ GEM activesupport builder htmlentities (4.3.4) + http (1.0.4) + 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) i18n (0.7.0) i18n_data (0.7.0) inversion (0.12.3) @@ -253,6 +262,8 @@ GEM lumberjack (1.0.5) 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.1) mina (0.3.8) @@ -277,6 +288,7 @@ GEM multi_xml (0.5.5) multipart-post (2.0.0) mysql2 (0.4.2) + naught (1.1.0) netrc (0.11.0) nio4r (1.2.1) nokogiri (1.6.8) @@ -460,6 +472,7 @@ GEM sprockets-rails (~> 2.0) shoulda-matchers (2.6.1) activesupport (>= 3.0.0) + simple_oauth (0.3.1) simplecov (0.11.2) docile (~> 1.1.0) json (~> 1.8) @@ -500,6 +513,17 @@ GEM ttfunk (1.1.1) turbolinks (2.5.3) coffee-rails + twitter (5.16.0) + addressable (~> 2.3) + buftok (~> 0.2.0) + equalizer (= 0.0.10) + faraday (~> 0.9.0) + http (~> 1.0) + 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 (3.0.0) @@ -628,6 +652,7 @@ DEPENDENCIES timecop transitions turbolinks + twitter uglifier (>= 1.3.0) unobtrusive_flash (>= 3) web-console (~> 2.0) diff --git a/INSTALL.md b/INSTALL.md index cc363338..6caec0f7 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -52,6 +52,10 @@ There are a couple of environment variables you can set to configure OSEM. | CLOUDINARY_URL | *string* | Configure your cloudinary.com cloud name and api key/secret | STRIPE_PUBLISHABLE_KEY | *string* | Publishable Key for Stripe Gateway | STRIPE_SECRET_KEY | *string* | Secret Key for Stripe Gateway +| OSEM_TWITTER_CONSUMER_KEY | *string* | To embed tweets about the conference in the conference wide page +| OSEM_TWITTER_CONSUMER_SECRET | *string* | To embed tweets about the conference in the conference wide page +| OSEM_TWITTER_ACCESS_TOKEN | *string* | To embed tweets about the conference in the conference wide page +| OSEM_TWITTER_ACCESS_TOKEN_SECRET | *string* | To embed tweets about the conference in the conference wide page ### Online Ticket Payments We use [Stripe](https://stripe.com) for accepting your ticket payments securely over the web. @@ -71,6 +75,14 @@ $ bundle exec rake logo:reprocess ### openID In order to use [openID](http://openid.net/) logins for your OSEM installation you need to register your application with the providers ([Google](https://code.google.com/apis/console#:access), [GitHub](https://github.com/settings/applications/new) or [Facebook](https://developers.facebook.com/)) and enter their API keys in `config/secrets.yml` file, changing the existing sample values. +### Twitter + In order to embed conference tweets in the application you need to register your application here [twitter](https://apps.twitter.com/) and get twitter consumer and access keys. + Add your Twitter API keys in `.env` file into these variables: + OSEM_TWITTER_CONSUMER_KEY="" + OSEM_TWITTER_CONSUMER_SECRET="" + OSEM_TWITTER_ACCESS_TOKEN="" + OSEM_TWITTER_ACCESS_TOKEN_SECRET="" + ## Recurring Jobs ======= Open a separate terminal and go into the directory where the rails app is present, and type the following to start the delayed_jobs worker for sending email notifications. diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 8853d6e6..d29bc4eb 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -45,6 +45,7 @@ //= require osem-commercials //= require unobtrusive_flash //= require unobtrusive_flash_bootstrap +//= require refresh_current_events $(document).ready(function() { $('a[disabled=disabled]').click(function(event){ diff --git a/app/assets/javascripts/refresh_current_events.js b/app/assets/javascripts/refresh_current_events.js new file mode 100644 index 00000000..24a509b1 --- /dev/null +++ b/app/assets/javascripts/refresh_current_events.js @@ -0,0 +1,32 @@ +$(document).ready(function() { + // will call refreshCurrentEvents every 15 minutes + setInterval(refreshCurrentEvents,900000); + + // Only show the first child of tweet and sponsor containers on page load + $('#tweet-container').children().hide(); + $('#tweet-container').children().eq(0).show(); + $('#sponsor-container').children().hide(); + $('#sponsor-container').children().eq(0).show(); + + setInterval(function(){ cycleVisibleElement($('#tweet-container'))}, 5000) + setInterval(function(){ cycleVisibleElement($('#sponsor-container'))}, 10000) +}); + +// Makes an ajax call for fresh data +function refreshCurrentEvents(){ + $.ajax({ + url: "conference_info.js" + }); +}; + +// Cycle through children in a container and toggles visibility +function cycleVisibleElement(container){ + var childrens = $(container).children(); + for(var i = 0; i < childrens.length; i++ ){ + if(childrens.eq(i).is(":visible")){ + childrens.eq(i).hide(); + childrens.eq(++i % childrens.length).show(); + return + } + } +}; diff --git a/app/assets/stylesheets/osem.css.scss b/app/assets/stylesheets/osem.css.scss index d2a2cd10..462fa955 100644 --- a/app/assets/stylesheets/osem.css.scss +++ b/app/assets/stylesheets/osem.css.scss @@ -90,3 +90,20 @@ p.comment-body { .changeset{ display: none; } + +#conference-header{ + margin: auto; + width: 304px; + .conference-heading{ + float: left; + width: 150px; + } + .conference-logo{ + float: left; + width: 150px; + img{ + width: 100px; + height: 100px; + } + } +} diff --git a/app/controllers/admin/conferences_controller.rb b/app/controllers/admin/conferences_controller.rb index b7063e6e..67836ec5 100644 --- a/app/controllers/admin/conferences_controller.rb +++ b/app/controllers/admin/conferences_controller.rb @@ -173,8 +173,25 @@ module Admin end end + def conference_info + # To display sponsors in the conference wide information page + @sponsors = @conference.sponsors + @tweets = twitter_client.search_tweets(15, @conference.contact.social_tag) + @current_event_schedules = @program.selected_schedule.event_schedules.current.group_by{|es| es.event.track.name} + + respond_to do |format| + format.html{render layout: 'conference_info'} + format.js {render action: 'conference_info.js.haml'} + end + end + private + # To create a twitter client + def twitter_client + @twitter_client ||= TwitterWrapper.new + end + def conference_params params.require(:conference).permit(:title, :short_title, :description, :timezone, :start_date, :end_date, :rooms_attributes, :tracks_attributes, diff --git a/app/models/event_schedule.rb b/app/models/event_schedule.rb index b83e7c8c..32456bae 100644 --- a/app/models/event_schedule.rb +++ b/app/models/event_schedule.rb @@ -14,6 +14,7 @@ class EventSchedule < ActiveRecord::Base scope :confirmed, -> { joins(:event).where('state = ?', 'confirmed') } scope :canceled, -> { joins(:event).where('state = ?', 'canceled') } scope :withdrawn, -> { joins(:event).where('state = ?', 'withdrawn') } + scope :current, -> { where('start_time <= ?', Time.current).select{ |es| es.end_time >= Time.current }} delegate :guid, to: :room, prefix: true diff --git a/app/models/twitter_wrapper.rb b/app/models/twitter_wrapper.rb new file mode 100644 index 00000000..d4887720 --- /dev/null +++ b/app/models/twitter_wrapper.rb @@ -0,0 +1,21 @@ +class TwitterWrapper + attr_accessor :client + + def initialize + @client = Twitter::REST::Client.new do |config| + config.consumer_key = ENV['OSEM_TWITTER_CONSUMER_KEY'] + config.consumer_secret = ENV['OSEM_TWITTER_CONSUMER_SECRET'] + config.access_token = ENV['OSEM_TWITTER_ACCESS_TOKEN'] + config.access_token_secret = ENV['OSEM_TWITTER_ACCESS_TOKEN_SECRET'] + end + end + + ## + # Fetches tweets from twitter api based on search_term and no_of_tweets + # + def search_tweets(no_of_tweets, search_term) + @client.search(search_term, result_type: 'recent').take(no_of_tweets).collect + rescue + nil + end +end diff --git a/app/views/admin/conferences/_conference_tweets.html.haml b/app/views/admin/conferences/_conference_tweets.html.haml new file mode 100644 index 00000000..99f1be51 --- /dev/null +++ b/app/views/admin/conferences/_conference_tweets.html.haml @@ -0,0 +1,13 @@ +.container + .row + .col-md-12.text-center + %h2 + = "##{@conference.contact.social_tag} Tweets" + %div#tweet-container + - @tweets.each do |tweet| + %div#tweet + %blockquote.twitter-tweet + %p{:lang => "en"} + = tweet.text + = link_to "— @" + tweet.user.name, "http://www.twitter.com/#{tweet.user.name}", target: "_blank" + = link_to tweet.url, tweet.url.to_s diff --git a/app/views/admin/conferences/_current_events.html.haml b/app/views/admin/conferences/_current_events.html.haml new file mode 100644 index 00000000..109b96af --- /dev/null +++ b/app/views/admin/conferences/_current_events.html.haml @@ -0,0 +1,14 @@ +.container + .row.row-centered + - @current_event_schedules.each do |track, event_schedules| + .col-md-4.col-sm-4.col-centered + %h2.text-center + = track + - event_schedules.each do |event_schedule| + %h3.text-center + = event_schedule.event.title + %h4.text-center + presented by + = event_schedule.event.speaker_names + in + = event_schedule.event.room.name diff --git a/app/views/admin/conferences/_sponsors.html.haml b/app/views/admin/conferences/_sponsors.html.haml new file mode 100644 index 00000000..5fb6a8c7 --- /dev/null +++ b/app/views/admin/conferences/_sponsors.html.haml @@ -0,0 +1,12 @@ +.container + .row + .col-md-12.text-center + %h2 Sponsors + %div#sponsor-container + - @conference.sponsorship_levels.each do |sponsorship_level| + -if sponsorship_level.sponsors.any? + - sponsorship_level.sponsors.each_slice(3) do |slice| + .row.row-centered#sponsor + - slice.each do |sponsor| + .col-md-4.col-sm-4.col-centered.col-top + = image_tag get_logo(sponsor), class: "img-responsive img-sponsor img-sponsor-#{sponsorship_level.position}" diff --git a/app/views/admin/conferences/conference_info.html.haml b/app/views/admin/conferences/conference_info.html.haml new file mode 100644 index 00000000..21c7bbc3 --- /dev/null +++ b/app/views/admin/conferences/conference_info.html.haml @@ -0,0 +1,23 @@ +.container + - if @conference.present? + .row + .col-md-12.text-center + %div#conference-header + %div.conference-logo + = image_tag(@conference.picture_url, class: 'img-responsive') if @conference.picture? + %div.conference-heading + %h1 + = @conference.title + %span + = @conference.short_title + - if @conference.description.present? + %h3 + = @conference.description + - if @current_event_schedules.present? + .row#current-events + = render 'current_events' + - if @conference.contact.social_tag.present? && @tweets.any? + .row#conference-tweets + = render 'conference_tweets' + - if @sponsors.any? + = render 'sponsors' diff --git a/app/views/admin/conferences/conference_info.js.haml b/app/views/admin/conferences/conference_info.js.haml new file mode 100644 index 00000000..694d5ace --- /dev/null +++ b/app/views/admin/conferences/conference_info.js.haml @@ -0,0 +1,4 @@ +$('#current-events').html("#{escape_javascript(render 'current_events', data: @current_events)}"); +$('#conference-tweets').html("#{escape_javascript(render 'conference_tweets', data: @tweets)}"); +$('#tweet-container').children().hide(); +$('#tweet-container').children().eq(0).show(); diff --git a/app/views/layouts/_admin_sidebar.html.haml b/app/views/layouts/_admin_sidebar.html.haml index b1e4730c..0419e54c 100644 --- a/app/views/layouts/_admin_sidebar.html.haml +++ b/app/views/layouts/_admin_sidebar.html.haml @@ -139,3 +139,9 @@ = link_to(admin_conference_roles_path(@conference.short_title)) do %span.fa.fa-group Roles + + - if can? :show, @conference + %li{:class=> active_nav_li(conference_info_admin_conference_path(@conference.short_title))} + = link_to(conference_info_admin_conference_path(@conference.short_title)) do + %span.fa.fa-expand + Conference Info diff --git a/app/views/layouts/conference_info.html.haml b/app/views/layouts/conference_info.html.haml new file mode 100644 index 00000000..21b710c0 --- /dev/null +++ b/app/views/layouts/conference_info.html.haml @@ -0,0 +1,25 @@ +%html{:xmlns => "http://www.w3.org/1999/html"} + %head + %meta{:charset => "utf-8"} + %meta{:name => "viewport", :content => "width=device-width, initial-scale=1, maximum-scale=1"} + %title= content_for?(:title) ? yield(:title) : (ENV['OSEM_NAME'] || 'OSEM') + %meta{:content => "", :name => "description"} + %meta{:content => "", :name => "author"} + = stylesheet_link_tag "application", :media => "all" + = javascript_include_tag "application" + + = csrf_meta_tags + :javascript + window.liveSettings = { + api_key: "#{ENV['OSEM_TRANSIFEX_APIKEY']}", + picker: "bottom-right", + detectlang: true, + autocollect: true + }; + = content_for(:script_head) + = javascript_include_tag "//cdn.transifex.com/live.js" + + = yield(:head) + %body + .container + = yield diff --git a/config/routes.rb b/config/routes.rb index 2a44f93a..1b878dbb 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -26,6 +26,9 @@ Osem::Application.routes.draw do end resources :comments, only: [:index] resources :conferences do + member do + get :conference_info + end resource :contact, except: [:index, :new, :create, :show, :destroy] resources :schedules, only: [:index, :create, :show, :update, :destroy] resources :event_schedules, only: [:create, :update, :destroy] @@ -103,7 +106,7 @@ Osem::Application.routes.draw do get '/revision_history/:id/revert_attribute' => 'versions#revert_attribute', as: 'revision_history_revert_attribute' end - resources :conferences, only: [:index, :show] do + resources :conferences, only: [:index, :show ] do resource :program, only: [] do resources :proposals, except: :destroy do get 'commercials/render_commercial' => 'commercials#render_commercial' diff --git a/dotenv.example b/dotenv.example index ec2715ea..92c51f6d 100644 --- a/dotenv.example +++ b/dotenv.example @@ -57,3 +57,9 @@ OSEM_SMTP_DOMAIN="" # Enable the usage of the devise ichain plugin OSEM_ICHAIN_ENABLED=false + +# To embed tweets about the conference in the conference wide screen +OSEM_TWITTER_CONSUMER_KEY="" +OSEM_TWITTER_CONSUMER_SECRET="" +OSEM_TWITTER_ACCESS_TOKEN="" +OSEM_TWITTER_ACCESS_TOKEN_SECRET="" diff --git a/spec/factories/event_schedule.rb b/spec/factories/event_schedule.rb index 80abe677..598034b7 100644 --- a/spec/factories/event_schedule.rb +++ b/spec/factories/event_schedule.rb @@ -20,5 +20,20 @@ FactoryGirl.define do event_schedule.schedule = program.selected_schedule end end + factory :current_event_schedule do + after(:build) do |event_schedule| + event_schedule.start_time = DateTime.current + end + end + factory :past_event_schedule do + after(:build) do |event_schedule| + event_schedule.start_time = (DateTime.current - 2.days).to_s + end + end + factory :future_event_schedule do + after(:build) do |event_schedule| + event_schedule.start_time = (DateTime.current + 2.days).to_s + end + end end end diff --git a/spec/models/event_schedule_spec.rb b/spec/models/event_schedule_spec.rb index 28b04ddf..78a18aaa 100644 --- a/spec/models/event_schedule_spec.rb +++ b/spec/models/event_schedule_spec.rb @@ -1,6 +1,12 @@ require 'spec_helper' describe EventSchedule do + subject { create(:event_schedule) } + let(:schedule) { create(:schedule) } + let(:program) { create(:program, selected_schedule: schedule) } + let!(:current_event_schedule) { create(:current_event_schedule, schedule: program.selected_schedule) } + let!(:past_event_schedule) { create(:past_event_schedule) } + let!(:future_event_schedule) { create(:future_event_schedule) } describe 'association' do it { should belong_to(:schedule) } @@ -18,4 +24,12 @@ describe EventSchedule do it { is_expected.to validate_presence_of(:room) } it { is_expected.to validate_presence_of(:start_time) } end + + describe 'scope' do + context 'current' do + it 'returns only current events' do + expect(program.selected_schedule.event_schedules.current).to match_array([current_event_schedule]) + end + end + end end diff --git a/spec/models/schedule_spec.rb b/spec/models/schedule_spec.rb index 6d1f7346..b9cc3a82 100644 --- a/spec/models/schedule_spec.rb +++ b/spec/models/schedule_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe Schedule do - describe 'association' do it { should belong_to(:program) } it { should have_many(:event_schedules).dependent(:destroy) }