diff --git a/ruby/lib/jam_ruby.rb b/ruby/lib/jam_ruby.rb index 141cff16b..76d2e8e02 100755 --- a/ruby/lib/jam_ruby.rb +++ b/ruby/lib/jam_ruby.rb @@ -32,8 +32,8 @@ require "jam_ruby/lib/s3_util" require "jam_ruby/lib/s3_manager" require "jam_ruby/lib/profanity" require "jam_ruby/lib/json_validator" -require "jam_ruby/lib/em_helper.rb" -require "jam_ruby/lib/nav.rb" +require "jam_ruby/lib/em_helper" +require "jam_ruby/lib/nav" require "jam_ruby/resque/audiomixer" require "jam_ruby/resque/icecast_config_writer" require "jam_ruby/resque/resque_hooks" @@ -64,6 +64,7 @@ require "jam_ruby/app/uploaders/mix_uploader" require "jam_ruby/app/uploaders/music_notation_uploader" require "jam_ruby/lib/desk_multipass" require "jam_ruby/amqp/amqp_connection_manager" +require "jam_ruby/database" require "jam_ruby/message_factory" require "jam_ruby/models/feedback" require "jam_ruby/models/feedback_observer" diff --git a/ruby/lib/jam_ruby/constants/validation_messages.rb b/ruby/lib/jam_ruby/constants/validation_messages.rb index 20edf028d..724eb5680 100644 --- a/ruby/lib/jam_ruby/constants/validation_messages.rb +++ b/ruby/lib/jam_ruby/constants/validation_messages.rb @@ -74,6 +74,7 @@ module ValidationMessages # music sessions MUST_BE_A_MUSICIAN = "must be a musician" CLAIMED_RECORDING_ALREADY_IN_PROGRESS = "already started by someone else" + MUST_BE_KNOWN_TIMEZONE = "not valid" # notification DIFFERENT_SOURCE_TARGET = 'can\'t be same as the sender' diff --git a/ruby/lib/jam_ruby/database.rb b/ruby/lib/jam_ruby/database.rb new file mode 100644 index 000000000..25f7098bc --- /dev/null +++ b/ruby/lib/jam_ruby/database.rb @@ -0,0 +1,21 @@ +module JamRuby + # creates messages (implementation: protocol buffer) objects cleanly + class Database + + + #def self.db_timezone + # @@db_timezone ||= TZInfo::Timezone.get(fetch_db_timezone) + #end + + def self.db_timezone + @@db_timezone ||= ActiveSupport::TimeZone.new(fetch_db_timezone) + end + + def self.fetch_db_timezone + result = ActiveRecord::Base.connection.execute('show timezone') + tz = result.getvalue(0, 0) + result.clear + tz + end + end +end \ No newline at end of file diff --git a/ruby/lib/jam_ruby/models/music_session.rb b/ruby/lib/jam_ruby/models/music_session.rb index ae4fcdec6..a2b57874c 100644 --- a/ruby/lib/jam_ruby/models/music_session.rb +++ b/ruby/lib/jam_ruby/models/music_session.rb @@ -10,7 +10,7 @@ module JamRuby RECURRING_MODES = [NO_RECURRING, RECURRING_WEEKLY] - attr_accessor :legal_terms, :language_description, :scheduled_start_time, :access_description + attr_accessor :legal_terms, :language_description, :access_description attr_accessor :approved_rsvps, :open_slots, :pending_invitations @@ -49,10 +49,15 @@ module JamRuby validates :is_unstructured_rsvp, :inclusion => {:in => [true, false]} validates :legal_terms, :inclusion => {:in => [true]}, :on => :create validates :creator, :presence => true + validates :timezone, presence: true, if: Proc.new { |session| session.scheduled_start } + validates :scheduled_duration, presence: true, if: Proc.new { |session| session.scheduled_start } + validate :creator_is_musician + validate :validate_timezone before_create :generate_share_token before_create :add_to_feed + #before_save :update_scheduled_start SHARE_TOKEN_LENGTH = 8 @@ -63,6 +68,15 @@ module JamRuby feed.music_session = self end + + def update_scheduled_start + + # it's very important that this only run if timezone changes, or scheduled_start changes + if self.scheduled_start && (self.scheduled_start_changed? || self.timezone_changed?) + self.scheduled_start = MusicSession.parse_scheduled_start(self.scheduled_start, self.timezone) + end + end + def comment_count self.comments.size end @@ -246,7 +260,7 @@ module JamRuby where rr.user_id = '#{user.id}' ) )} - ) + ).order(:scheduled_start) end def self.create user, options @@ -270,8 +284,7 @@ module JamRuby ms.open_rsvps = options[:open_rsvps] if options[:open_rsvps] ms.creator = user ms.is_unstructured_rsvp = options[:isUnstructuredRsvp] if options[:isUnstructuredRsvp] - ms.scheduled_start = options[:start] - ms.scheduled_start = parse_scheduled_start(ms.scheduled_start, ms.timezone) + ms.scheduled_start = parse_scheduled_start(options[:start], options[:timezone]) if options[:start] && options[:timezone] ms.save @@ -439,14 +452,6 @@ module JamRuby tz end - def scheduled_start_time - unless self.scheduled_start.nil? - self.scheduled_start.utc.strftime "%a %e %B %Y" - else - "" - end - end - def scheduled_end_time end @@ -708,7 +713,7 @@ module JamRuby [music_sessions, user_scores] end - # converts the passed scheduled_start into the UTC using the specified timezone offset. + # converts the passed scheduled_start into the database timezone using the specified timezone offset. # timezone comes in as TIMEZONE DISPLAY, TIMEZONE ID def self.parse_scheduled_start(scheduled_start, timezone_param) @@ -718,14 +723,94 @@ module JamRuby index = timezone_param.rindex(',') if index tz_identifier = timezone_param[(index + 1)..-1] - timezone = TZInfo::Timezone.get(tz_identifier) - result = timezone.local_to_utc(scheduled_start, true) + begin + timezone = ActiveSupport::TimeZone.new(tz_identifier) + rescue Exception => e + @@log.error("unable to find timezone=#{tz_identifier}, e=#{e}") + end + + if timezone + begin + # first convert the time provided, and convert to the specified timezone (local_to_utc) + # then, convert that to the system timezone, under the ASSUMPTION that the database is configured to use the system timezone + # you can get into trouble if your dev database is not using the system timezone of the web machine + + result = timezone.parse(scheduled_start) + rescue Exception => e + @@log.error("unable to convert #{scheduled_start} to #{timezone}, e=#{e}") + puts "unable to convert #{scheduled_start} to #{timezone}, e=#{e}" + end + end end end result end + def scheduled_start_time + if scheduled_start + scheduled_start.utc.strftime "%a %e %B %Y" + else + "" + end + end + + # should create a timestamp like: + # + # with_timezone = TRUE + # Tuesday, April 29, 8:00-9:00 PM TIMEZONE (where TIMEZONE is the TIMEZONE defined in the MusicSession when it was created) + # + # with_timezone = FALSE + # Thursday, July 10 - 10:00pm + # this should be in a helper + def pretty_scheduled_start(with_timezone) + if scheduled_start && + start_time = scheduled_start + timezone_display = 'UTC' + index = timezone.rindex(',') + if index + tz_display = timezone[0, index] + tz_identifier = timezone[(index + 1)..-1] + begin + tz = TZInfo::Timezone.get(tz_identifier) + rescue Exception => e + @@log.error("unable to find timezone=#{tz_identifier}, e=#{e}") + end + + if tz + begin + start_time = tz.utc_to_local(scheduled_start.utc) + timezone_display = tz_display + rescue Exception => e + @@log.error("unable to convert #{scheduled_start} to #{tz}, e=#{e}") + puts "unable to convert #{e}" + end + end + end + duration = scheduled_duration + # you can put seconds into the scheduled_duration field, but once stored, it comes back out as a string + if scheduled_duration.class == String + begin + bits = scheduled_duration.split(':') + duration = bits[0].to_i.hours + bits[1].to_i.minutes + bits[2].to_i.seconds + rescue Exception => e + duration = 1.hours + @@log.error("unable to parse duration #{scheduled_duration}") + end + end + end_time = start_time + duration + if with_timezone + "#{start_time.strftime("%A, %B %e")}, #{start_time.strftime("%l:%M").strip}-#{end_time.strftime("%l:%M %p").strip} #{timezone_display}" + else + "#{start_time.strftime("%A, %B %e")} - #{start_time.strftime("%l:%M%P").strip}" + end + + else + "Date and time TBD" + end + + end + private def generate_share_token @@ -747,5 +832,19 @@ module JamRuby end end + def validate_timezone + if timezone + index = timezone.rindex(',') + if index + tz_identifier = timezone[(index + 1)..-1] + begin + TZInfo::Timezone.get(tz_identifier) + rescue Exception => e + @@log.error("unable to find timezone=#{tz_identifier}, e=#{e}") + errors.add(:timezone, ValidationMessages::MUST_BE_KNOWN_TIMEZONE) + end + end + end + end end end diff --git a/ruby/spec/factories.rb b/ruby/spec/factories.rb index 2241ff7df..7cf0753bb 100644 --- a/ruby/spec/factories.rb +++ b/ruby/spec/factories.rb @@ -89,13 +89,14 @@ FactoryGirl.define do musician_access true legal_terms true language 'eng' - timezone 'utc' + timezone 'UTC,Etc/UTC' legal_policy 'standard' recurring_mode 'once' genre JamRuby::Genre.first association :creator, :factory => :user open_rsvps true scheduled_start Time.now + scheduled_duration 3600 factory :recurring_music_session_weekly do recurring_mode 'weekly' diff --git a/ruby/spec/jam_ruby/models/music_session_spec.rb b/ruby/spec/jam_ruby/models/music_session_spec.rb index a14db9f57..c3d155148 100644 --- a/ruby/spec/jam_ruby/models/music_session_spec.rb +++ b/ruby/spec/jam_ruby/models/music_session_spec.rb @@ -48,28 +48,70 @@ describe MusicSession do end describe "create" do - it "scheduled session" do - session = MusicSession.create(creator, { - name: "session 1", - description: "my session", - genres: ['ambient'], - musician_access: true, - fan_access: true, - approval_required: true, - fan_chat: true, - legal_policy: 'Standard', - language: 'eng', - start: "Thu Jul 10 2014 10:00 PM", - timezone: "Central Time (US & Canada),America/Chicago", - open_rsvps: true, - legal_terms: true, - recurring_mode: 'once', - isUnstructuredRsvp: true, - rsvp_slots: [{ instrument_id: "other", proficiency_level: 1, approve: true}] - }) + + let(:open_params) { + { + name: "session 1", + description: "my session", + genres: ['ambient'], + musician_access: true, + fan_access: true, + approval_required: true, + fan_chat: true, + legal_policy: 'Standard', + language: 'eng', + start: "Thu Jul 10 2014 10:00 PM", + duration: 30, + timezone: "Central Time (US & Canada),America/Chicago", + open_rsvps: true, + legal_terms: true, + recurring_mode: 'once', + isUnstructuredRsvp: true, + rsvp_slots: [{ instrument_id: "other", proficiency_level: 1, approve: true}] + } + } + + it "wide open scheduled session" do + session = MusicSession.create(creator, open_params) session.valid?.should be_true # verify that scheduled_start is now 5 hours ahead of what was specified (CST during summer is -5 offset) session.scheduled_start.utc.should == DateTime.new(2014,07,11,3,00,0) + + # verify that the update_scheduled_start does not disturb scheduled_start + session.save! + session.scheduled_start.utc.should == DateTime.new(2014,07,11,3,00,0) + end + + it "works with UTC timezone" do + open_params[:timezone] = 'UTC,Etc/UTC' + session = MusicSession.create(creator, open_params) + session.valid?.should be_true + # verify that scheduled_start is now 5 hours ahead of what was specified (CST during summer is -5 offset) + session.scheduled_start.should == DateTime.new(2014,07,10,22,00,0) + end + + it "no scheduled_start" do + open_params[:timezone] = nil + open_params[:scheduled_start] = nil + open_params[:scheduled_duration] = nil + session = MusicSession.create(creator, open_params) + session.valid?.should be_true + session.scheduled_start.should be_nil + end + end + + describe "pretty_scheduled_start" do + it "displays central time correctly" do + time = MusicSession.parse_scheduled_start("Thu Jul 10 2014 10:00 PM", "Central Time (US & Canada),America/Chicago") + music_session = FactoryGirl.create(:music_session, scheduled_start: time, timezone: "Central Time (US & Canada),America/Chicago") + music_session.pretty_scheduled_start(true).should == 'Thursday, July 10, 10:00-11:00 PM Central Time (US & Canada)' + music_session.pretty_scheduled_start(false).should == 'Thursday, July 10 - 10:00pm' + end + + it "displays default correctly" do + music_session = FactoryGirl.create(:music_session, scheduled_start: nil) + music_session.pretty_scheduled_start(true).should == 'Date and time TBD' + music_session.pretty_scheduled_start(false).should == 'Date and time TBD' end end @@ -258,8 +300,6 @@ describe MusicSession do creators_slot.proficiency_level = nil creators_slot.save! - puts RsvpSlot.all.inspect - music_session = MusicSession.find(music_session1.id) approved_rsvps = music_session.approved_rsvps approved_rsvps.length.should == 1 @@ -278,7 +318,7 @@ describe MusicSession do approved_rsvps.length.should == 2 # find the user who made the request for 2 rsvp slots in the approved_users array - approved_some_user = approved_rsvps.find {|s| puts s.inspect; s.id == some_user.id} + approved_some_user = approved_rsvps.find {|s| s.id == some_user.id} instrument_ids = JSON.parse(approved_some_user[:instrument_ids]) instrument_ids.should =~ rsvp_request.rsvp_slots.map {|slot| slot.instrument_id } @@ -294,17 +334,29 @@ describe MusicSession do describe "parse_scheduled_start" do - it "converts correctly" do + it "converts central time correctly" do # CST has -5 offset in summery - time = DateTime.new(2004,10,15,1,30,0) + time = DateTime.new(2004,10,15,1,30,0).strftime('%Y-%m-%d %H:%M:%S') converted = MusicSession.parse_scheduled_start(time, 'Central Time (US & Canada),America/Chicago') - converted.should == DateTime.new(2004,10,15,6,30,0) + converted.should == DateTime.new(2004,10,15,6,30,0, '+0') # CST has -6 offset in winter - time = DateTime.new(2004,11,15,1,30,0) + time = DateTime.new(2004,11,15,1,30,0).strftime('%Y-%m-%d %H:%M:%S') converted = MusicSession.parse_scheduled_start(time, 'Central Time (US & Canada),America/Chicago') - converted.should == DateTime.new(2004,11,15,7,30,0) + converted.should == DateTime.new(2004,11,15,7,30,0, '+0') + end + + it "converts UTC correctly" do + # should not shift + time = DateTime.new(2004,10,15,1,30,0, '+0').strftime('%Y-%m-%d %H:%M:%S') + converted = MusicSession.parse_scheduled_start(time, 'UTC,Etc/UTC') + converted.should == DateTime.new(2004,10,15,1,30,0, '+0') + + # should not shift + time = DateTime.new(2004,11,15,1,30,0).strftime('%Y-%m-%d %H:%M:%S') + converted = MusicSession.parse_scheduled_start(time, 'UTC,Etc/UTC') + converted.should == DateTime.new(2004,11,15,1,30,0, '+0') end end diff --git a/web/app/assets/javascripts/scheduled_session.js b/web/app/assets/javascripts/scheduled_session.js index 1369a60ea..efba4ce61 100644 --- a/web/app/assets/javascripts/scheduled_session.js +++ b/web/app/assets/javascripts/scheduled_session.js @@ -104,7 +104,7 @@ var options = { id: session.id, name: session.name, - scheduled_start: session.scheduled_start + scheduled_start: session.pretty_scheduled_start_short }; var txt = $(context._.template($('#template-scheduled-session').html(), options, { variable: 'data' })); $scheduledSessions.append(txt); diff --git a/web/app/controllers/api_music_sessions_controller.rb b/web/app/controllers/api_music_sessions_controller.rb index ad7df710c..ca84595f0 100644 --- a/web/app/controllers/api_music_sessions_controller.rb +++ b/web/app/controllers/api_music_sessions_controller.rb @@ -194,23 +194,22 @@ class ApiMusicSessionsController < ApiController begin @music_session = MusicSession.find(params[:id]) if @music_session.creator == current_user - band = Band.find(options[:band]) unless params[:band_id].nil? - @music_session.name = params[:name] unless params[:name].nil? - @music_session.description = params[:description] unless params[:description].nil? - @music_session.musician_access = params[:musician_access] unless params[:musician_access].nil? - @music_session.approval_required = params[:approval_required] unless params[:approval_required].nil? - @music_session.fan_chat = params[:fan_chat] unless params[:fan_chat].nil? - @music_session.fan_access = params[:fan_access] unless params[:fan_access].nil? - @music_session.genre = Genre.find_by_id(params[:genres][0]) if params[:genres] && params[:genres].length > 0 - @music_session.legal_policy = params[:legal_policy] unless params[:legal_policy].nil? - @music_session.language = params[:language] unless params[:language].nil? - @music_session.scheduled_start = params[:start] unless params[:start].nil? - @music_session.scheduled_duration = params[:duration] + ' minutes' if params[:duration] - @music_session.timezone = params[:timezone] unless params[:timezone].nil? - @music_session.recurring_mode = params[:reoccurrence] unless params[:reoccurrence].nil? - @music_session.open_rsvps = params[:open_rsvps] ? true : false - @music_session.band = band unless band.nil? + @music_session.name = params[:name] if params.include? :name + @music_session.description = params[:description] if params.include? :description + @music_session.musician_access = params[:musician_access] if params.include? :musician_access + @music_session.approval_required = params[:approval_required] if params.include? :approval_required + @music_session.fan_chat = params[:fan_chat] if params.include? :fan_chat + @music_session.fan_access = params[:fan_access] if params.include? :fan_access + @music_session.genre = Genre.find_by_id(params[:genres][0]) if params.include?(:genres) && params[:genres] && params[:genres].length > 0 + @music_session.legal_policy = params[:legal_policy] if params.include? :legal_policy + @music_session.language = params[:language] if params.include? :language + @music_session.scheduled_start = MusicSession.parse_scheduled_start(params[:start], params[:timezone]) if params.include?(:start) && params.include?(:timezone) + @music_session.scheduled_duration = params[:duration] + ' minutes' if params.include? :duration + @music_session.timezone = params[:timezone] if params.include? :timezone + @music_session.recurring_mode = params[:reoccurrence] if params.include? :reoccurrence + @music_session.open_rsvps = params[:open_rsvps] if params.include? :open_rsvps + @music_session.band = (params[:band] ? Band.find(params[:band]) : nil) if params.include? :band @music_session.save params[:music_notations].each do |notation_id| @@ -219,7 +218,7 @@ class ApiMusicSessionsController < ApiController notation.save ms.music_notations << notation - end if params[:music_notations] + end if params.include? :music_notations if @music_session.errors.any? response.status = :unprocessable_entity diff --git a/web/app/helpers/music_session_helper.rb b/web/app/helpers/music_session_helper.rb index 8fa30836f..e556b01c5 100644 --- a/web/app/helpers/music_session_helper.rb +++ b/web/app/helpers/music_session_helper.rb @@ -62,4 +62,12 @@ module MusicSessionHelper def timezone_list end + + def scheduled_start_time(music_session) + music_session.scheduled_start_time + end + + def pretty_scheduled_start(music_session, with_timezone) + music_session.pretty_scheduled_start(with_timezone) + end end diff --git a/web/app/views/api_music_sessions/show_history.rabl b/web/app/views/api_music_sessions/show_history.rabl index 6ee368d9a..50d64fb07 100644 --- a/web/app/views/api_music_sessions/show_history.rabl +++ b/web/app/views/api_music_sessions/show_history.rabl @@ -31,10 +31,24 @@ else [item.genre.description] # XXX: need to return single genre; not array end + node :scheduled_start_time do |session| + scheduled_start_time(session) + end + node :scheduled_start do |history| history.scheduled_start.utc.strftime("%a %e %B %Y %H:%M:%S") if history.scheduled_start end + node :pretty_scheduled_start_with_timezone do |session| + pretty_scheduled_start(session, true) + end + + node :pretty_scheduled_start_short do|session| + pretty_scheduled_start(session, false) + end + + + child(:creator => :creator) { attributes :id, :name, :photo_url } diff --git a/web/spec/factories.rb b/web/spec/factories.rb index c8244c948..f6e44a967 100644 --- a/web/spec/factories.rb +++ b/web/spec/factories.rb @@ -114,7 +114,8 @@ FactoryGirl.define do open_rsvps true scheduled_start Time.now recurring_mode 'once' - scheduled_duration "30 minutes" + scheduled_duration 3600 + timezone "UTC,Etc/UTC" factory :recurring_music_session_weekly do diff --git a/web/spec/requests/active_music_sessions_api_spec.rb b/web/spec/requests/active_music_sessions_api_spec.rb index a7821bd4e..de4fae3ed 100755 --- a/web/spec/requests/active_music_sessions_api_spec.rb +++ b/web/spec/requests/active_music_sessions_api_spec.rb @@ -27,7 +27,8 @@ describe "Active Music Session API ", :type => :api do :genres => ["classical"], :musician_access => true, :approval_required => false, :fan_chat => true, :fan_access => true, :legal_policy => true, :language => 'eng', - :timezone => "utc", + :timezone => "UTC,Etc/UTC", + :duration => "60", :rsvp_slots => [{proficiency_level: 1, instrument_id: 'other', approve:true}] } } diff --git a/web/spec/requests/music_sessions_api_spec.rb b/web/spec/requests/music_sessions_api_spec.rb index 2ef66969c..7a4f59c85 100644 --- a/web/spec/requests/music_sessions_api_spec.rb +++ b/web/spec/requests/music_sessions_api_spec.rb @@ -23,7 +23,8 @@ describe "Scheduled Music Session API ", :type => :api do :genres => ["classical"], :musician_access => true, :approval_required => false, :fan_chat => true, :fan_access => true, :legal_policy => true, :language => 'eng', - :timezone => "utc", + :timezone => "UTC,Etc/UTC", + :duration => "60", :rsvp_slots => [{proficiency_level: 1, instrument_id: 'other', approve:true}] } } before(:all) do