jam-cloud/ruby/lib/jam_ruby/models/music_session.rb

1199 lines
45 KiB
Ruby
Raw Normal View History

2014-05-27 03:52:59 +00:00
require 'iso-639'
module JamRuby
class MusicSession < ActiveRecord::Base
include HtmlSanitize
html_sanitize strict: [:name, :description]
@@log = Logging.logger[MusicSession]
2014-05-15 17:22:21 +00:00
NO_RECURRING = 'once'
RECURRING_WEEKLY = 'weekly'
RECURRING_MODES = [NO_RECURRING, RECURRING_WEEKLY]
UNSTARTED_INTERVAL_DAYS_SKIP = '14' # days past scheduled start to skip in query
UNSTARTED_INTERVAL_DAYS_PURGE = '28' # days past scheduled start to purge session
UNSTARTED_INTERVAL_DAYS_PURGE_RECUR = '28' # days past scheduled start to purge recurddingsession
CREATE_TYPE_START_SCHEDULED = 'start-scheduled'
CREATE_TYPE_SCHEDULE_FUTURE = 'schedule-future'
CREATE_TYPE_RSVP = 'rsvp'
CREATE_TYPE_IMMEDIATE = 'immediately'
CREATE_TYPE_QUICK_START = 'quick-start'
CREATE_TYPE_LESSON = 'lesson'
CREATE_TYPE_QUICK_PUBLIC = 'quick-public'
attr_accessor :legal_terms, :language_description, :access_description, :scheduling_info_changed
2014-05-29 16:32:22 +00:00
2014-06-15 19:49:43 +00:00
attr_accessor :approved_rsvps, :open_slots, :pending_invitations
2014-05-06 22:50:41 +00:00
self.table_name = "music_sessions"
2014-03-09 13:38:46 +00:00
2014-05-06 13:34:38 +00:00
self.primary_key = 'id'
belongs_to :creator,:class_name => 'JamRuby::User', :foreign_key => :user_id, :inverse_of => :music_session_histories
belongs_to :band, :class_name => 'JamRuby::Band', :foreign_key => :band_id, :inverse_of => :music_sessions
belongs_to :active_music_session, :class_name => 'JamRuby::ActiveMusicSession', foreign_key: :music_session_id
belongs_to :session_controller, :class_name => 'JamRuby::User', :foreign_key => :session_controller_id, :inverse_of => :controlled_sessions
belongs_to :lesson_session, :class_name => "JamRuby::LessonSession"
2014-05-06 13:34:38 +00:00
has_many :music_session_user_histories, :class_name => "JamRuby::MusicSessionUserHistory", :foreign_key => "music_session_id", :dependent => :delete_all
has_many :comments, :class_name => "JamRuby::MusicSessionComment", :foreign_key => "music_session_id"
2014-05-24 07:58:47 +00:00
has_many :session_info_comments, :class_name => "JamRuby::SessionInfoComment", :foreign_key => "music_session_id"
2014-05-06 13:34:38 +00:00
has_many :likes, :class_name => "JamRuby::MusicSessionLiker", :foreign_key => "session_id"
has_many :plays, :class_name => "JamRuby::PlayablePlay", :as => :playable, :dependent => :destroy
has_one :share_token, :class_name => "JamRuby::ShareToken", :inverse_of => :shareable, :foreign_key => 'shareable_id'
has_one :feed, :class_name => "JamRuby::Feed", :inverse_of => :music_session, :foreign_key => 'music_session_id', :dependent => :destroy
belongs_to :genre, :class_name => "JamRuby::Genre", :inverse_of => :music_sessions, :foreign_key => 'genre_id'
has_many :join_requests, :foreign_key => "music_session_id", :inverse_of => :music_session, :class_name => "JamRuby::JoinRequest"
has_many :invitations, :foreign_key => "music_session_id", :inverse_of => :music_session, :class_name => "JamRuby::Invitation"
2014-05-06 13:34:38 +00:00
has_many :invited_musicians, :through => :invitations, :class_name => "JamRuby::User", :foreign_key => "receiver_id", :source => :receiver
has_many :fan_invitations, :foreign_key => "music_session_id", :inverse_of => :music_session, :class_name => "JamRuby::FanInvitation"
2014-05-06 13:34:38 +00:00
has_many :invited_fans, :through => :fan_invitations, :class_name => "JamRuby::User", :foreign_key => "receiver_id", :source => :receiver
2014-05-19 15:35:38 +00:00
has_many :rsvp_slots, :class_name => "JamRuby::RsvpSlot", :foreign_key => "music_session_id", :dependent => :destroy
2014-05-26 05:35:57 +00:00
has_many :music_notations, :class_name => "JamRuby::MusicNotation", :foreign_key => "music_session_id"
2016-01-04 03:38:30 +00:00
has_many :jam_track_session, :class_name => "JamRuby::JamTrackSession"
2017-04-14 19:34:18 +00:00
has_many :broadcasts, :class_name => "JamRuby::Broadcast"
validates :genre, :presence => true
2013-07-26 08:07:24 +00:00
validates :description, :presence => true, :no_profanity => true
validates :fan_chat, :inclusion => {:in => [true, false]}
validates :fan_access, :inclusion => {:in => [true, false]}
validates :approval_required, :inclusion => {:in => [true, false]}
validates :musician_access, :inclusion => {:in => [true, false]}
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
2014-05-06 13:34:38 +00:00
before_create :generate_share_token
#before_save :update_scheduled_start
before_save :check_scheduling_info_changed
2014-05-06 13:34:38 +00:00
SHARE_TOKEN_LENGTH = 8
2014-05-06 13:34:38 +00:00
SEPARATOR = '|'
2017-04-14 19:34:18 +00:00
def current_broadcast
Broadcast.current_broadcast(self)
end
def create_broadcast(google_client, user, broadcast_options)
broadcast = current_broadcast
if broadcast.nil?
broadcast = create_youtube_broadcast(google_client, user, broadcast_options)
else
refresh_youtube_broadcast(google_client, user, broadcast)
# check against Youtube the real state of broadcast, to see if we need a new one?
end
broadcast
end
def create_stream(google_client, user, broadcast_options)
broadcast = create_broadcast(google_client, user, broadcast_options)
stream = current_stream(broadcast)
if stream.nil?
create_youtube_stream(google_client, user, broadcast, broadcast_options)
bind_broadcast(google_client, user, broadcast)
else
bind_broadcast(google_client, user, broadcast)
end
end
def current_stream(broadcast)
broadcast.stream_id
end
def refresh_youtube_broadcast(google_client, user, broadcast)
broadcast_data = google_client.get_broadcast(user, broadcast.broadcast_id)
broadcast.broadcast_status = broadcast_data["status"]["lifeCycleStatus"]
broadcast.broadcast_data = broadcast_data.to_json
end
# https://developers.google.com/youtube/v3/live/docs/liveStreams#resource
def create_youtube_stream(google_client, user, broadcast, broadcast_options)
# https://developers.google.com/youtube/v3/live/docs/liveStreams/insert
# required
# snippet.title
# cdn.format
# cdn.ingestionType (deprecated - use resolution/framerate)
stream_options = {}
stream_options[:snippet] ||= {}
stream_options[:snippet][:title] ||= name
stream_options[:snippet][:isDefaultStream] = false
#broadcast_options[:snippet][:scheduledEndTime] = end_time.utc.iso8601
stream_options[:cdn] ||= {}
stream_options[:cdn][:frameRate] ||= '30fps'
stream_options[:cdn][:resolution] ||= '360p'
stream_options[:cdn][:ingestionType] ||= 'rtmp'
stream_options[:contentDetails] ||= {}
stream_options[:contentDetails][:isReusable] = false
stream_options = google_client.create_stream(user, stream_options)
broadcast.stream_id = stream_options["id"]
broadcast.stream_status = stream_options["status"]["streamStatus"]
broadcast.stream_name = stream_options["cdn"]["ingestionInfo"]["streamName"]
broadcast.stream_address = stream_options["cdn"]["ingestionInfo"]["ingestionAddress"]
broadcast.stream_data = stream_options.to_json
broadcast.save!
broadcast
end
def create_youtube_broadcast(google_client, user, broadcast_options)
start_time, end_time = youtube_times
broadcast_options ||= {}
broadcast_options[:snippet] ||= {}
broadcast_options[:snippet][:title] ||= name
broadcast_options[:snippet][:description] ||= description
broadcast_options[:snippet][:scheduledStartTime] = start_time.utc.iso8601
#broadcast_options[:snippet][:scheduledEndTime] = end_time.utc.iso8601
broadcast_options[:status] ||= {}
broadcast_options[:status][:privacyStatus] ||= (fan_access ? 'public' : 'private')
broadcast_options[:contentDetails] ||= {}
# if false, this causes a 'request not authorized error'
# From: https://developers.google.com/youtube/v3/live/docs/liveBroadcasts
# If your channel does not have permission to disable recordings, and you attempt to insert a broadcast with the recordFromStart property set to false, the API will return a Forbidden error.
#broadcast_options[:contentDetails][:recordFromStart] ||= false
broadcast_data = google_client.create_broadcast(user, broadcast_options)
broadcast = Broadcast.new
broadcast.music_session_id = self.id
broadcast.user_id = user.id
broadcast.broadcast_id = broadcast_data["id"]
broadcast.broadcast_status = broadcast_data["status"]["lifeCycleStatus"]
broadcast.broadcast_data = broadcast_data.to_json
broadcast.save!
broadcast
end
def bind_broadcast(google_client, user, broadcast)
bind_data = google_client.bind_broadcast(user, broadcast.broadcast_id, broadcast.stream_id)
broadcast.broadcast_data = bind_data.to_json
broadcast.save!
broadcast
end
def youtube_times
start = scheduled_start_time
if start < Time.now
start = Time.now
end_time = start + safe_scheduled_duration
return [start, end_time]
else
return [start, scheduled_end_time]
end
end
def check_scheduling_info_changed
@scheduling_info_changed = scheduled_start_changed?
true
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
2014-05-06 13:34:38 +00:00
def comment_count
self.comments.size
end
# copies all relevant info for the recurring session
def copy
MusicSession.transaction do
# copy base music_session data
new_session = MusicSession.new
new_session.description = self.description
new_session.user_id = self.user_id
new_session.band_id = self.band_id
new_session.fan_access = self.fan_access
new_session.scheduled_start = self.scheduled_start + 1.week
new_session.scheduled_duration = self.scheduled_duration
new_session.musician_access = self.musician_access
new_session.approval_required = self.approval_required
new_session.fan_chat = self.fan_chat
new_session.genre_id = self.genre_id
new_session.legal_policy = self.legal_policy
new_session.language = self.language
new_session.name = self.name
new_session.recurring_mode = self.recurring_mode
new_session.timezone = self.timezone
new_session.open_rsvps = self.open_rsvps
new_session.is_unstructured_rsvp = self.is_unstructured_rsvp
new_session.legal_terms = true
new_session.session_controller = self.session_controller
2014-06-04 05:47:28 +00:00
# copy rsvp_slots, rsvp_requests, and rsvp_requests_rsvp_slots
RsvpSlot.where("music_session_id = '#{self.id}'").find_each do |slot|
new_slot = RsvpSlot.new
new_slot.instrument_id = slot.instrument_id
new_slot.proficiency_level = slot.proficiency_level
new_slot.is_unstructured_rsvp = slot.is_unstructured_rsvp
2014-06-05 04:55:15 +00:00
new_session.rsvp_slots << new_slot
2014-06-04 05:47:28 +00:00
# get the request for this slot that was approved (should only be ONE)
rsvp_request_slot = RsvpRequestRsvpSlot.where("chosen = true AND rsvp_slot_id = ?", slot.id).first
unless rsvp_request_slot.nil?
rsvp = RsvpRequest.find_by_id(rsvp_request_slot.rsvp_request_id)
new_rsvp = RsvpRequest.new
new_rsvp.user_id = rsvp.user_id
new_rsvp_req_slot = RsvpRequestRsvpSlot.new
2014-06-05 04:55:15 +00:00
new_rsvp_req_slot.rsvp_request = new_rsvp
new_rsvp_req_slot.rsvp_slot = new_slot
2014-06-04 05:47:28 +00:00
new_rsvp_req_slot.chosen = true
# .last => new_slot above
new_session.rsvp_slots.last.rsvp_requests_rsvp_slots << new_rsvp_req_slot
# if this slot was not chosen, try to get any RSVPs that were 1-time cancellations and copy those
else
rejected_req_slots = RsvpRequestRsvpSlot.where("(chosen is null OR chosen = FALSE) AND rsvp_slot_id = ?", slot.id).order("created_at ASC")
rejected_req_slots.each do |req_slot|
# get RsvpRequest corresponding to this RsvpRequestRsvpSlot
rsvp = RsvpRequest.find_by_id(req_slot.rsvp_request_id)
# if the RSVP was canceled (but all future sessions were NOT canceled), then copy this one and break
if rsvp.canceled && !rsvp.cancel_all
new_rsvp = RsvpRequest.new
new_rsvp.user_id = rsvp.user_id
new_rsvp_req_slot = RsvpRequestRsvpSlot.new
2014-06-05 04:55:15 +00:00
new_rsvp_req_slot.rsvp_request = new_rsvp
new_rsvp_req_slot.rsvp_slot = new_slot
new_rsvp_req_slot.chosen = nil
# .last => new_slot above
new_session.rsvp_slots.last.rsvp_requests_rsvp_slots << new_rsvp_req_slot
end
2014-06-04 05:47:28 +00:00
end
end
end
# copy music_notations
MusicNotation.where("music_session_id = '#{self.id}'").find_each do |notation|
new_notation = MusicNotation.new
new_notation.user_id = notation.user_id
2014-06-05 04:55:15 +00:00
new_notation.music_session = new_session
new_notation.file_url = notation.file_url
new_notation.file_name = notation.file_name
new_notation.size = notation.size
2014-06-05 04:55:15 +00:00
new_session.music_notations << new_notation
end
new_session.save!
# mark the next session as scheduled
self.next_session_scheduled = true
self.save
return new_session
end
end
def is_lesson?
!!lesson_session
end
2014-05-06 13:34:38 +00:00
def grouped_tracks
tracks = []
self.music_session_user_histories.each do |msuh|
user = User.find(msuh.user_id)
# see if user already exists in array
t = tracks.select { |track| track.musician.id == user.id }.first
if t.blank?
t = Track.new
t.musician = user
t.instrument_ids = []
# this treats each track as a "user", which has 1 or more instruments in the session
unless msuh.instruments.blank?
instruments = msuh.instruments.split(SEPARATOR)
instruments.each do |instrument|
if !t.instrument_ids.include? instrument
t.instrument_ids << instrument
end
end
end
tracks << t
# this handles case where user is duplicated in MSUH for the same session
else
unless msuh.instruments.blank?
instruments = msuh.instruments.split(SEPARATOR)
instruments.each do |instrument|
if !t.instrument_ids.include? instrument
t.instrument_ids << instrument
end
2014-05-06 13:34:38 +00:00
end
end
end
2014-01-05 03:47:23 +00:00
end
2014-05-06 13:34:38 +00:00
tracks
2014-01-05 03:47:23 +00:00
end
2014-05-24 07:58:47 +00:00
def can_join? user, as_musician
if as_musician
unless user.musician
return false # "a fan can not join a music session as a musician"
end
if self.musician_access
if self.approval_required
return self.invited_musicians.exists?(user.id) || self.approved_rsvps.include?(user)
2014-05-24 07:58:47 +00:00
else
return true
end
else
# the creator can always join, and the invited users can join
return self.creator == user || self.invited_musicians.exists?(user.id) || self.approved_rsvps.include?(user)
2014-05-24 07:58:47 +00:00
end
else
# it's a fan, and the only way a fan can join is if fan_access is true
self.fan_access
end
end
def can_see? user
if self.musician_access || self.fan_access
true
else
self.creator == user || self.invited_musicians.exists?(user.id)
end
end
def set_session_controller(current_user, user)
# only allow update of session controller by the creator or the currently marked user
should_tick = false
if current_user != creator && current_user != self.session_controller
return should_tick
end
if active_music_session
if user
if active_music_session.users.exists?(user)
self.session_controller = user
should_tick = save
end
else
self.session_controller = nil
should_tick = save
end
end
should_tick
end
2014-05-06 13:34:38 +00:00
def self.index(current_user, user_id, band_id = nil, genre = nil)
hide_private = false
if current_user.id != user_id
hide_private = false # TODO: change to true once public flag exists
end
2012-12-06 01:56:12 +00:00
query = MusicSession
2014-05-06 13:34:38 +00:00
.joins(
2012-12-05 19:10:58 +00:00
%Q{
2012-12-06 01:56:12 +00:00
LEFT OUTER JOIN
2014-05-06 13:34:38 +00:00
music_sessions_user_history
2012-12-06 01:56:12 +00:00
ON
2014-05-06 22:50:41 +00:00
music_sessions.id = music_sessions_user_history.music_session_id
2012-12-06 01:56:12 +00:00
}
)
2014-05-06 13:34:38 +00:00
.where(
%Q{
2014-05-06 22:50:41 +00:00
music_sessions.user_id = '#{user_id}'
2012-12-06 01:56:12 +00:00
}
)
2014-05-06 13:34:38 +00:00
#query = query.where("public = false") unless !hide_private
2014-05-06 22:50:41 +00:00
query = query.where("music_sessions.band_id = '#{band_id}") unless band_id.nil?
query = query.where("music_sessions.genres like '%#{genre}%'") unless genre.nil?
2012-12-06 01:56:12 +00:00
return query
end
2012-11-02 06:51:52 +00:00
2015-05-28 13:20:14 +00:00
def self.scheduled user, only_public = false
# keep unstarted sessions around for 12 hours after scheduled_start
session_not_started = "(music_sessions.scheduled_start > NOW() - '12 hour'::INTERVAL AND music_sessions.started_at IS NULL)"
# keep started sessions that are not finished yet
session_started_not_finished = "(music_sessions.started_at IS NOT NULL AND music_sessions.session_removed_at IS NULL)"
# let session be restarted for up to 2 hours after finishing
session_finished = "(music_sessions.session_removed_at > NOW() - '2 hour'::INTERVAL)"
query = MusicSession.joins(
%Q{
LEFT OUTER JOIN
invitations
ON
music_sessions.id = invitations.music_session_id AND invitations.receiver_id = '#{user.id}'
}
)
query = query.where("music_sessions.canceled = FALSE")
2015-05-28 13:20:14 +00:00
query = query.where('music_sessions.fan_access = TRUE or music_sessions.musician_access = TRUE') if only_public
#query = query.where("music_sessions.user_id = '#{user.id}' OR invitations.id IS NOT NULL")
query = query.where("music_sessions.id in (
select distinct(rs.music_session_id)
from rsvp_slots rs
where rs.id in (
select rrrs.rsvp_slot_id
from rsvp_requests rr
inner join rsvp_requests_rsvp_slots rrrs on rr.id = rrrs.rsvp_request_id
where rr.user_id = '#{user.id}'AND rrrs.chosen = true
)
) OR invitations.id IS NOT NULL OR music_sessions.user_id = '#{user.id}'")
query = query.where("music_sessions.scheduled_start IS NULL OR #{session_not_started} OR #{session_finished} OR #{session_started_not_finished}")
query = query.where("music_sessions.create_type IS NULL OR (music_sessions.create_type != '#{CREATE_TYPE_QUICK_START}' AND music_sessions.create_type != '#{CREATE_TYPE_QUICK_PUBLIC}')")
2014-05-21 19:11:50 +00:00
query = query.order("music_sessions.scheduled_start ASC")
2014-06-24 15:33:31 +00:00
query
2014-05-21 19:11:50 +00:00
end
# if only_approved is set, then only return sessions where the current user has been chosen
def self.scheduled_rsvp(user, only_approved = false)
filter_approved = only_approved ? 'AND rrrs.chosen = true' : ''
MusicSession.where(%Q{music_sessions.canceled = FALSE AND
(music_sessions.create_type is NULL OR (music_sessions.create_type != '#{CREATE_TYPE_QUICK_START}' AND music_sessions.create_type != '#{CREATE_TYPE_QUICK_PUBLIC}')) AND
(music_sessions.scheduled_start is NULL OR music_sessions.scheduled_start > NOW() - '4 hour'::INTERVAL) AND
music_sessions.id in (
select distinct(rs.music_session_id)
from rsvp_slots rs
where rs.id in (
select rrrs.rsvp_slot_id
from rsvp_requests rr
inner join rsvp_requests_rsvp_slots rrrs on rr.id = rrrs.rsvp_request_id
where rr.user_id = '#{user.id}' #{filter_approved}
)
)}
).order(:scheduled_start)
end
2014-05-19 15:35:38 +00:00
def self.create user, options
2014-12-03 07:08:26 +00:00
band = Band.where(id: options[:band]).first if options[:band].present?
ms = MusicSession.new
2014-05-19 15:35:38 +00:00
ms.name = options[:name]
ms.description = options[:description]
ms.genre_id = (options[:genres].length > 0 ? options[:genres][0] : nil) if options[:genres]
ms.musician_access = options[:musician_access]
ms.approval_required = options[:approval_required]
ms.fan_access = options[:fan_access]
ms.fan_chat = options[:fan_chat]
ms.band = band
2014-05-19 15:35:38 +00:00
ms.legal_policy = options[:legal_policy]
ms.language = options[:language]
2014-06-18 22:15:56 +00:00
ms.scheduled_duration = options[:duration].to_i * 1.minutes if options[:duration]
2014-05-19 15:35:38 +00:00
ms.recurring_mode = options[:recurring_mode] if options[:recurring_mode]
2014-06-19 18:43:21 +00:00
ms.timezone = options[:timezone] if options[:timezone]
ms.legal_terms = true
ms.open_rsvps = options[:open_rsvps] if options[:open_rsvps]
ms.creator = user
ms.session_controller = user
ms.create_type = options[:create_type]
ms.is_unstructured_rsvp = options[:isUnstructuredRsvp] if options[:isUnstructuredRsvp]
ms.scheduled_start = parse_scheduled_start(options[:start], options[:timezone]) if options[:start] && options[:timezone]
ms.scheduled_start = options[:scheduled_start] if options[:scheduled_start]
if options[:lesson_session]
ms.lesson_session = options[:lesson_session]
end
ms.save
2014-05-19 15:35:38 +00:00
unless ms.errors.any?
ms.reload
2014-06-18 22:15:56 +00:00
rsvp_slot_ids = []
self_rsvp_slot_ids = []
options[:rsvp_slots].each do |rs|
rsvp = RsvpSlot.new
rsvp.instrument = Instrument.find(rs[:instrument_id])
rsvp.proficiency_level = rs[:proficiency_level]
rsvp.music_session = ms
rsvp.save
ms.rsvp_slots << rsvp
2014-06-19 18:43:21 +00:00
if rs[:approve] == true
2014-06-18 22:15:56 +00:00
self_rsvp_slot_ids.push rsvp.id
else
rsvp_slot_ids.push rsvp.id
end
end if options[:rsvp_slots]
2014-06-19 18:43:21 +00:00
RsvpRequest.create({session_id: ms.id, rsvp_slots: self_rsvp_slot_ids, :autoapprove => true}, user)
2014-05-19 15:35:38 +00:00
options[:invitations].each do |invite_id|
invitation = Invitation.new
receiver = User.find(invite_id)
invitation.sender = user
invitation.receiver = receiver
invitation.music_session = ms
invitation.save
ms.invitations << invitation
# send scheduled session notification or join session notification
case ms.create_type
when CREATE_TYPE_RSVP, CREATE_TYPE_SCHEDULE_FUTURE
Notification.send_scheduled_session_invitation(ms, receiver)
when CREATE_TYPE_LESSON
if ms.lesson_session.is_active?
Notification.send_jamclass_invitation_teacher(ms, receiver)
end
else
Notification.send_session_invitation(receiver, user, ms.id)
end
2014-05-19 15:35:38 +00:00
end if options[:invitations]
options[:music_notations].each do |notation|
notation = MusicNotation.find(notation[:id])
2014-05-19 15:35:38 +00:00
notation.music_session = ms
notation.save
ms.music_notations << notation
end if options[:music_notations]
ms.save
end
ms
end
def self.update user, options
music_session = MusicSession.find(options[:id])
if music_session.creator == current_user
Notification.send_scheduled_session_cancelled music_session
music_session.destroy
respond_with responder: ApiResponder, :status => 204
else
render :json => { :message => ValidationMessages::PERMISSION_VALIDATION_ERROR }, :status => 404
end
end
2014-05-06 13:34:38 +00:00
def unique_users
User
.joins(:music_session_user_histories)
.group("users.id")
.order("users.id")
.where(%Q{ music_sessions_user_history.music_session_id = '#{id}'})
2012-11-02 06:51:52 +00:00
end
2014-05-06 13:34:38 +00:00
# returns one user history per user, with instruments all crammed together, and with total duration
def unique_user_histories
# only get the active users if the session is in progress
user_filter = "music_sessions_user_history.session_removed_at is null" if self.session_removed_at.nil?
2014-05-06 13:34:38 +00:00
MusicSessionUserHistory
.joins(:user)
.select("STRING_AGG(instruments, '|') AS total_instruments,
SUM(date_part('epoch', COALESCE(music_sessions_user_history.session_removed_at, music_sessions_user_history.created_at) - music_sessions_user_history.created_at)) AS total_duration,
music_sessions_user_history.user_id, music_sessions_user_history.music_session_id, users.first_name, users.last_name, users.photo_url")
.group("music_sessions_user_history.user_id, music_sessions_user_history.music_session_id, users.first_name, users.last_name, users.photo_url")
.order("music_sessions_user_history.user_id")
.where(%Q{ music_sessions_user_history.music_session_id = '#{id}'})
.where(user_filter)
2012-11-02 06:51:52 +00:00
end
2014-05-06 13:34:38 +00:00
def duration_minutes
end_time = self.session_removed_at || Time.now.utc
2014-05-06 13:34:38 +00:00
(end_time - self.created_at) / 60.0
2012-11-02 06:51:52 +00:00
end
2014-05-06 13:34:38 +00:00
def music_session_user_histories
@msuh ||= JamRuby::MusicSessionUserHistory
.where(:music_session_id => self.id)
2014-05-06 13:34:38 +00:00
.order('created_at DESC')
end
2014-05-06 13:34:38 +00:00
def comments
@comments ||= JamRuby::MusicSessionComment
.where(:music_session_id => self.id)
2014-05-06 13:34:38 +00:00
.order('created_at DESC')
end
2014-05-06 13:34:38 +00:00
def likes
@likes ||= JamRuby::MusicSessionLiker
.where(:music_session_id => self.music_session_id)
end
2014-05-06 13:34:38 +00:00
# these are 'users that are a part of this session'
# which means are currently in the music_session, or, rsvp'ed, or creator
def part_of_session? user
# XXX check RSVP'ed
user == self.creator || (active_music_session ? active_music_session.users.exists?(user.id) : false)
2014-01-05 03:47:23 +00:00
end
2014-05-06 13:34:38 +00:00
def is_over?
active_music_session.nil?
end
2014-05-06 13:34:38 +00:00
def has_mount?
!active_music_session.nil? && !active_music_session.mount.nil?
end
def can_cancel? user
self.creator == user
2014-05-24 07:58:47 +00:00
end
def legal_policy_url
2014-06-30 21:24:56 +00:00
# TODO: move to DB or config file or helper
case legal_policy.downcase
2014-05-24 07:58:47 +00:00
when "standard"
return "session-legal-policies/standard"
2014-05-24 07:58:47 +00:00
when "creative"
return "session-legal-policies/creativecommons"
2014-05-24 07:58:47 +00:00
when "offline"
return "session-legal-policies/offline"
2014-05-24 07:58:47 +00:00
when "jamtracks"
return "session-legal-policies/jamtracks"
2014-05-24 07:58:47 +00:00
else
return ""
2014-05-26 05:35:57 +00:00
end
end
2014-05-27 03:52:59 +00:00
def language_description
2014-05-29 05:28:40 +00:00
if self.language.blank?
self.language = "en"
2014-05-29 05:28:40 +00:00
end
iso639Details = ISO_639.find_by_code(self.language)
unless iso639Details.blank?
return iso639Details.english_name
else
return "English"
end
2014-05-27 03:52:59 +00:00
end
def scheduled_end_time
2017-04-14 19:34:18 +00:00
start = scheduled_start_time
duration = safe_scheduled_duration
start + duration
end
2014-07-12 01:54:54 +00:00
def timezone_id
MusicSession.split_timezone(timezone)[0]
end
2014-06-09 02:43:07 +00:00
def timezone_description
2014-07-12 01:54:54 +00:00
MusicSession.split_timezone(timezone)[1]
2014-06-09 02:43:07 +00:00
end
def musician_access_description
if self.musician_access && self.approval_required
"Musicians may join by approval"
elsif self.musician_access && !self.approval_required
"Musicians may join at will"
elsif !self.musician_access && !self.approval_required
"Only RSVP musicians may join"
2014-05-27 04:32:14 +00:00
end
2014-06-09 02:43:07 +00:00
end
2014-05-27 04:32:14 +00:00
2014-06-09 02:43:07 +00:00
def fan_access_description
if self.fan_access && self.fan_chat
"Fans may listen, chat with the band"
elsif self.fan_access && !self.fan_chat
"Fans may listen, chat with each other"
elsif !self.fan_access && !self.fan_chat
"Fans may not listen to session"
2014-05-27 04:32:14 +00:00
end
2014-06-09 02:43:07 +00:00
end
2014-05-27 04:32:14 +00:00
2014-06-09 02:43:07 +00:00
def access_description
"#{musician_access_description}. #{fan_access_description}."
2014-05-27 04:32:14 +00:00
end
# retrieve users that have approved RSVPs
2014-05-26 05:35:57 +00:00
def approved_rsvps
2014-08-19 19:23:41 +00:00
User.find_by_sql(%Q{select distinct ON(u.id) u.id, u.photo_url, u.first_name, u.last_name, u.last_jam_audio_latency, json_agg(ii.id) as instrument_ids, json_agg(ii.description) as instrument_descriptions, json_agg(rs.proficiency_level) as instrument_proficiencies, json_agg(rr.id) as rsvp_request_ids
2014-06-19 18:43:21 +00:00
from rsvp_slots rs
inner join rsvp_requests_rsvp_slots rrrs on rrrs.rsvp_slot_id = rs.id
inner join rsvp_requests rr on rrrs.rsvp_request_id = rr.id
left join instruments ii on ii.id = rs.instrument_id
2014-06-19 18:43:21 +00:00
inner join users u on u.id = rr.user_id
where rrrs.chosen = true AND rs.music_session_id = '#{self.id}' AND rr.canceled != TRUE
group by u.id order by u.id}
2014-06-19 18:43:21 +00:00
)
end
# get all slots for this session and perform a set difference with all chosen slots;
# this will return those that are not filled yet
# this method excludes rsvp_slots marked as 'is_unstructured_rsvp = true'
2014-05-26 05:35:57 +00:00
def open_slots
2014-06-19 18:43:21 +00:00
RsvpSlot.find_by_sql(%Q{select rs.*, ii.description
from rsvp_slots rs
inner join instruments ii on ii.id = rs.instrument_id
where rs.music_session_id = '#{self.id}'
and rs.is_unstructured_rsvp = false
2014-06-19 18:43:21 +00:00
except
select distinct rs.*, iii.description
from rsvp_slots rs
inner join instruments iii on iii.id = rs.instrument_id
inner join rsvp_requests_rsvp_slots rrrs on rrrs.rsvp_slot_id = rs.id
where rs.music_session_id = '#{self.id}'
2014-05-26 18:00:34 +00:00
and rrrs.chosen = true
}
)
2014-05-26 05:35:57 +00:00
end
# retrieve users that have invitations but have not submitted an RSVP request for this session
2014-05-26 05:35:57 +00:00
def pending_invitations
User.find_by_sql(%Q{select distinct u.id, u.email, u.photo_url, u.first_name, u.last_name, u.online
from users u
inner join invitations i on u.id = i.receiver_id
2014-05-26 05:35:57 +00:00
left join rsvp_requests rr on rr.user_id = i.receiver_id
where i.music_session_id = '#{self.id}'
and rr.user_id is null}
)
2014-05-24 07:58:47 +00:00
end
# retrieve pending RsvpRequests
def pending_rsvp_requests
RsvpRequest.index(self, nil, {status: 'pending'})
end
2014-05-06 13:34:38 +00:00
def recordings
Recording.where(music_session_id: self.id)
2014-01-05 03:47:23 +00:00
end
2014-05-06 13:34:38 +00:00
def end_history
self.update_attribute(:session_removed_at, Time.now)
2014-01-05 03:47:23 +00:00
2014-05-06 13:34:38 +00:00
# ensure all user histories are closed
music_session_user_histories.each do |music_session_user_history|
music_session_user_history.end_history
# then update any users that need their user progress updated
if music_session_user_history.duration_minutes > 15 && music_session_user_history.max_concurrent_connections >= 3
music_session_user_history.user.update_progression_field(:first_real_music_session_at)
end
end
end
2014-05-06 13:34:38 +00:00
def self.removed_music_session(session_id)
hist = self
.where(:id => session_id)
2014-05-06 13:34:38 +00:00
.limit(1)
.first
hist.end_history if hist
Notification.send_session_ended(session_id)
2014-03-09 13:38:46 +00:00
end
2014-05-06 13:34:38 +00:00
def remove_non_alpha_num(token)
token.gsub(/[^0-9A-Za-z]/, '')
2014-03-09 13:38:46 +00:00
end
def tag
nil unless has_attribute?(:tag)
a = read_attribute(:tag)
a.nil? ? nil : a.to_i
end
def latency
nil unless has_attribute?(:latency)
a = read_attribute(:latency)
a.nil? ? nil : a.to_i
end
# initialize the two temporary tables we use to drive sms_index and sms_users
def self.sms_init(current_user, options = {}, include_pending=false)
2014-08-18 15:37:55 +00:00
session_id = options[:session_id] || 'any'
2014-08-18 15:37:55 +00:00
my_locidispid = current_user.last_jam_locidispid
# 13 is an average audio gear value we use if they have not qualified any gear
2014-08-18 15:37:55 +00:00
my_audio_latency = current_user.last_jam_audio_latency || 13
locidispid_expr = my_locidispid ? "#{my_locidispid}::bigint" : '0::bigint' # Have to pass in zero; NULL fails silently in the stored proc
self.connection.execute("SELECT sms_index('#{current_user.id}'::varchar, #{locidispid_expr}, #{my_audio_latency}::integer, #{ActiveRecord::Base.connection.quote(session_id)}::varchar, #{include_pending}::boolean)").check
end
# Generate a list of music sessions (that are active) filtered by genre, language, keyword, and sorted
# (and tagged) by rsvp'd (1st), invited (2nd), and musician can join (3rd). within a group tagged the
# same, sorted by score. date seems irrelevant as these are active sessions. sms_init must be called
# first.
def self.sms_query(current_user, options = {})
client_id = options[:client_id]
genre = options[:genre]
lang = options[:lang]
keyword = options[:keyword]
offset = options[:offset]
limit = options[:limit]
day = options[:day]
timezone_offset = options[:timezone_offset]
query = MusicSession
.select('music_sessions.*')
# this is not really needed when sms_music_session_tmp is joined
# unless there is something specific we need out of active_music_sessions
# query = query.joins(
# %Q{
# INNER JOIN
# active_music_sessions
# ON
# active_music_sessions.id = music_sessions.id
# }
# )
# .select('1::integer as tag, 15::integer as latency')
# integrate sms_music_session_tmp into the processing
# then we can join sms_music_session_tmp and not join active_music_sessions
query = query.joins(
%Q{
INNER JOIN
sms_music_session_tmp
ON
sms_music_session_tmp.music_session_id = music_sessions.id
}
)
.select('sms_music_session_tmp.tag, sms_music_session_tmp.latency')
query = query.order(
%Q{
tag, latency, music_sessions.id
}
)
.group(
%Q{
tag, latency, music_sessions.id
}
)
# if not specified, default offset to 0
offset ||= 0
offset = offset.to_i
# if not specified, default limit to 20
limit ||= 20
limit = limit.to_i
query = query.offset(offset)
query = query.limit(limit)
query = query.where("music_sessions.create_type IS NULL OR (music_sessions.create_type != ? AND music_sessions.create_type != ? AND music_sessions.create_type != ?)", MusicSession::CREATE_TYPE_QUICK_START, MusicSession::CREATE_TYPE_IMMEDIATE, MusicSession::CREATE_TYPE_QUICK_PUBLIC)
query = query.where("music_sessions.genre_id = ?", genre) unless genre.blank?
query = query.where('music_sessions.language = ?', lang) unless lang.blank?
query = query.where("(description_tsv @@ to_tsquery('jamenglish', ?))", ActiveRecord::Base.connection.quote(keyword) + ':*') unless keyword.blank?
if !day.blank? && !timezone_offset.blank?
begin
day = Date.parse(day)
next_day = day + 1
timezone_offset = timezone_offset.to_i
if timezone_offset == 0
timezone_offset = '' # no offset to specify in this case
elsif timezone_offset > 0
timezone_offset = "+#{timezone_offset}"
end
query = query.where("scheduled_start BETWEEN TIMESTAMP WITH TIME ZONE '#{day} 00:00:00#{timezone_offset}'
AND TIMESTAMP WITH TIME ZONE '#{next_day} 00:00:00#{timezone_offset}'")
rescue Exception => e
# do nothing. bad date probably
@@log.warn("unable to parse day=#{day}, timezone_offset=#{timezone_offset}, e=#{e}")
end
else
sql =<<SQL
music_sessions.started_at IS NULL AND
(music_sessions.created_at > NOW() - interval '#{UNSTARTED_INTERVAL_DAYS_SKIP} days' OR
music_sessions.scheduled_start > NOW() - interval '#{UNSTARTED_INTERVAL_DAYS_SKIP} days')
SQL
query = query.where(sql)
end
return query
end
# returns the set of users in a music_sessions and the music_session they are in and their latency.
# sms_init must be called first.
# user.audio_latency / 2 , + other_user.audio_latency of them / 2, + network latency /2
def self.sms_users
2014-08-18 15:37:55 +00:00
return User.select('users.*, sms_users_tmp.music_session_id, sms_users_tmp.full_score, sms_users_tmp.audio_latency, sms_users_tmp.internet_score')
.joins(
%Q{
INNER JOIN
sms_users_tmp
ON
sms_users_tmp.user_id = users.id
}
)
.order('sms_users_tmp.music_session_id, sms_users_tmp.user_id')
end
# wrap me in a transaction!
# note that these queries must be actualized before the end of the transaction
# else the temporary tables created by sms_init will be gone.
def self.sms_index(current_user, params)
MusicSession.sms_init(current_user, params)
music_sessions = MusicSession.sms_query(current_user, params).all
music_session_users = MusicSession.sms_users.all
user_scores = {}
music_session_users.each do |user|
2014-08-18 15:37:55 +00:00
user_scores[user.id] = {full_score: user.full_score, audio_latency: user.audio_latency, internet_score: user.internet_score}
end
2014-08-18 15:37:55 +00:00
[music_sessions, user_scores]
end
2014-08-18 15:37:55 +00:00
# returns a single session, but populates any other user info with latency scores, so that show_history.rabl can do it's business
def self.session_with_scores(current_user, music_session_id, include_pending=false)
MusicSession.sms_init(current_user, {session_id: music_session_id}, include_pending)
2014-08-18 15:37:55 +00:00
music_session = MusicSession.find(music_session_id)
music_session_users = MusicSession.sms_users.all
user_scores = {}
music_session_users.each do |user|
user_scores[user.id] = {full_score: user.full_score, audio_latency: user.audio_latency, internet_score: user.internet_score}
end
[music_session, user_scores]
end
def self.upcoming_sessions
end
# converts the passed scheduled_start into the database timezone using the specified timezone offset.
2014-07-11 03:08:03 +00:00
# timezone comes in as TIMEZONE DISPLAY, TIMEZONE ID
def self.parse_scheduled_start(scheduled_start, timezone_param)
result = scheduled_start
2014-07-12 01:54:54 +00:00
tz_identifier = split_timezone(timezone_param)[0]
begin
timezone = ActiveSupport::TimeZone.new(tz_identifier)
rescue Exception => e
@@log.error("unable to find timezone=#{tz_identifier}, e=#{e}")
puts "unable to find timezone=#{tz_identifier}, e=#{e}"
2014-07-12 01:54:54 +00:00
end
2014-07-12 01:54:54 +00:00
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
2014-07-12 01:54:54 +00:00
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}"
2014-07-11 03:08:03 +00:00
end
end
result
end
def scheduled_start_date
if self.scheduled_start_time.blank?
""
else
self.scheduled_start.strftime "%a %e %B %Y"
end
end
def scheduled_start_time
if scheduled_start && scheduled_duration
start_time = scheduled_start
tz_identifier, tz_display = MusicSession.split_timezone(timezone)
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)
rescue Exception => e
@@log.error("unable to convert #{scheduled_start} to #{tz}, e=#{e}")
puts "unable to convert #{e}"
end
end
start_time
else
""
end
end
2014-07-12 01:54:54 +00:00
# takes our stored timezone which is DISPLAY,ID and returns an array of the two values (id first, then description)
def self.split_timezone(tz)
result = [nil, nil]
if tz
index = tz.rindex(',')
if index
tz_display = tz[0, index]
tz_identifier = tz[(index + 1)..-1]
result = [tz_identifier, tz_display]
end
end
result
end
Merge feature/calendaring branch: commit 8023d6481cbadd52e58b9a4342ac7636ce1807e3 VRFS-3276 : Hook calendar creation into user controller API. Add test to verify. commit 3a35002a46f870e2c490b88b3187e0b1569494fd VRFS-3276 : Calendar cleanup job * Add cleanup method to calendar manager * Create a daily job. * Add calendar cleanup to that job. * Add CRON entry * Daily job/ calendar cleanup test cases * Fix calendar manager spec for new required attribute commit 3ff5910f1f019ae8bcb5afe72a31f1d38bb7d7a3 VRFS-3276 : Add a delete-calendar directive when RSVP is canceled. VRFS-3276 : Include path to partial. This fails depending on the method used to start the web server. commit d2441cbf57e50895ac3b40534873c5d529cb3c4f VRFS-3276 : Test new calendar features. Use icalendar gem in test mode only to more deeply verify calendar in strict mode. commit 9ac272a0fb1e58d8cf9f02e7a0e04caada41f659 VRFS-3276 : Calendar manager updates to include manual calendars. Some refactoring to keep common stuff in one place. commit b5d0c758f0dcae41a5f24635e9da9ce6eda56670 VRFS-3276 : Schema, model updates and new calendar model. commit 20472b6b26c88c04edb9bc698e0c06c549e12eb5 VRFS-3276 : Change initial submit behavior of RSVP dialog to display calendar info. The user can then close the dialog after this prompt. commit 77c99103d0221f20ea342169821b90fa987ecf93 VRFS-3276 : Calendar feed markup and styling. Included as partial. commit e632f48600ae23b5f742773310b2a4ac16ae4ee8 VRFS-3276 : Routes and controller implementation of user calendar ICS feed, which uses calendar manager. commit 21fd80a188eae771a65333566b804ade795a1e8c VRFS-3276 : Initial tests for calendar manager commit 92a2524c65abf7b540f9d50049a1b760a5a9927f VRFS-3276 : Calendar manager * Streamline logic * Enable recurring sessions through rrule * Implement method to create ics feed for user * Extract a type-safe scheduled duration method on music_session for external and internal use. commit b71ad3a4cdd943eb84748abaa85fec263b9af468 VRFS-3276 : Include calendar manager commit f8eaafd03647613dafec9f9422282f8613d08e9a VRFS-3276 : Calendar Manager - initial checkin * Create ICS events given individual parameters * Create calendar from music session * Also will create ICS “delete” events
2015-07-06 20:34:27 +00:00
def safe_scheduled_duration
duration = scheduled_duration
Merge feature/calendaring branch: commit 8023d6481cbadd52e58b9a4342ac7636ce1807e3 VRFS-3276 : Hook calendar creation into user controller API. Add test to verify. commit 3a35002a46f870e2c490b88b3187e0b1569494fd VRFS-3276 : Calendar cleanup job * Add cleanup method to calendar manager * Create a daily job. * Add calendar cleanup to that job. * Add CRON entry * Daily job/ calendar cleanup test cases * Fix calendar manager spec for new required attribute commit 3ff5910f1f019ae8bcb5afe72a31f1d38bb7d7a3 VRFS-3276 : Add a delete-calendar directive when RSVP is canceled. VRFS-3276 : Include path to partial. This fails depending on the method used to start the web server. commit d2441cbf57e50895ac3b40534873c5d529cb3c4f VRFS-3276 : Test new calendar features. Use icalendar gem in test mode only to more deeply verify calendar in strict mode. commit 9ac272a0fb1e58d8cf9f02e7a0e04caada41f659 VRFS-3276 : Calendar manager updates to include manual calendars. Some refactoring to keep common stuff in one place. commit b5d0c758f0dcae41a5f24635e9da9ce6eda56670 VRFS-3276 : Schema, model updates and new calendar model. commit 20472b6b26c88c04edb9bc698e0c06c549e12eb5 VRFS-3276 : Change initial submit behavior of RSVP dialog to display calendar info. The user can then close the dialog after this prompt. commit 77c99103d0221f20ea342169821b90fa987ecf93 VRFS-3276 : Calendar feed markup and styling. Included as partial. commit e632f48600ae23b5f742773310b2a4ac16ae4ee8 VRFS-3276 : Routes and controller implementation of user calendar ICS feed, which uses calendar manager. commit 21fd80a188eae771a65333566b804ade795a1e8c VRFS-3276 : Initial tests for calendar manager commit 92a2524c65abf7b540f9d50049a1b760a5a9927f VRFS-3276 : Calendar manager * Streamline logic * Enable recurring sessions through rrule * Implement method to create ics feed for user * Extract a type-safe scheduled duration method on music_session for external and internal use. commit b71ad3a4cdd943eb84748abaa85fec263b9af468 VRFS-3276 : Include calendar manager commit f8eaafd03647613dafec9f9422282f8613d08e9a VRFS-3276 : Calendar Manager - initial checkin * Create ICS events given individual parameters * Create calendar from music session * Also will create ICS “delete” events
2015-07-06 20:34:27 +00:00
# you can put seconds into the scheduled_duration field, but once stored, it comes back out as a string
if scheduled_duration.class == String
duration = scheduled_duration.to_i.seconds
Merge feature/calendaring branch: commit 8023d6481cbadd52e58b9a4342ac7636ce1807e3 VRFS-3276 : Hook calendar creation into user controller API. Add test to verify. commit 3a35002a46f870e2c490b88b3187e0b1569494fd VRFS-3276 : Calendar cleanup job * Add cleanup method to calendar manager * Create a daily job. * Add calendar cleanup to that job. * Add CRON entry * Daily job/ calendar cleanup test cases * Fix calendar manager spec for new required attribute commit 3ff5910f1f019ae8bcb5afe72a31f1d38bb7d7a3 VRFS-3276 : Add a delete-calendar directive when RSVP is canceled. VRFS-3276 : Include path to partial. This fails depending on the method used to start the web server. commit d2441cbf57e50895ac3b40534873c5d529cb3c4f VRFS-3276 : Test new calendar features. Use icalendar gem in test mode only to more deeply verify calendar in strict mode. commit 9ac272a0fb1e58d8cf9f02e7a0e04caada41f659 VRFS-3276 : Calendar manager updates to include manual calendars. Some refactoring to keep common stuff in one place. commit b5d0c758f0dcae41a5f24635e9da9ce6eda56670 VRFS-3276 : Schema, model updates and new calendar model. commit 20472b6b26c88c04edb9bc698e0c06c549e12eb5 VRFS-3276 : Change initial submit behavior of RSVP dialog to display calendar info. The user can then close the dialog after this prompt. commit 77c99103d0221f20ea342169821b90fa987ecf93 VRFS-3276 : Calendar feed markup and styling. Included as partial. commit e632f48600ae23b5f742773310b2a4ac16ae4ee8 VRFS-3276 : Routes and controller implementation of user calendar ICS feed, which uses calendar manager. commit 21fd80a188eae771a65333566b804ade795a1e8c VRFS-3276 : Initial tests for calendar manager commit 92a2524c65abf7b540f9d50049a1b760a5a9927f VRFS-3276 : Calendar manager * Streamline logic * Enable recurring sessions through rrule * Implement method to create ics feed for user * Extract a type-safe scheduled duration method on music_session for external and internal use. commit b71ad3a4cdd943eb84748abaa85fec263b9af468 VRFS-3276 : Include calendar manager commit f8eaafd03647613dafec9f9422282f8613d08e9a VRFS-3276 : Calendar Manager - initial checkin * Create ICS events given individual parameters * Create calendar from music session * Also will create ICS “delete” events
2015-07-06 20:34:27 +00:00
end
2017-02-13 02:49:33 +00:00
if duration == 0
duration = 30 * 60
end
Merge feature/calendaring branch: commit 8023d6481cbadd52e58b9a4342ac7636ce1807e3 VRFS-3276 : Hook calendar creation into user controller API. Add test to verify. commit 3a35002a46f870e2c490b88b3187e0b1569494fd VRFS-3276 : Calendar cleanup job * Add cleanup method to calendar manager * Create a daily job. * Add calendar cleanup to that job. * Add CRON entry * Daily job/ calendar cleanup test cases * Fix calendar manager spec for new required attribute commit 3ff5910f1f019ae8bcb5afe72a31f1d38bb7d7a3 VRFS-3276 : Add a delete-calendar directive when RSVP is canceled. VRFS-3276 : Include path to partial. This fails depending on the method used to start the web server. commit d2441cbf57e50895ac3b40534873c5d529cb3c4f VRFS-3276 : Test new calendar features. Use icalendar gem in test mode only to more deeply verify calendar in strict mode. commit 9ac272a0fb1e58d8cf9f02e7a0e04caada41f659 VRFS-3276 : Calendar manager updates to include manual calendars. Some refactoring to keep common stuff in one place. commit b5d0c758f0dcae41a5f24635e9da9ce6eda56670 VRFS-3276 : Schema, model updates and new calendar model. commit 20472b6b26c88c04edb9bc698e0c06c549e12eb5 VRFS-3276 : Change initial submit behavior of RSVP dialog to display calendar info. The user can then close the dialog after this prompt. commit 77c99103d0221f20ea342169821b90fa987ecf93 VRFS-3276 : Calendar feed markup and styling. Included as partial. commit e632f48600ae23b5f742773310b2a4ac16ae4ee8 VRFS-3276 : Routes and controller implementation of user calendar ICS feed, which uses calendar manager. commit 21fd80a188eae771a65333566b804ade795a1e8c VRFS-3276 : Initial tests for calendar manager commit 92a2524c65abf7b540f9d50049a1b760a5a9927f VRFS-3276 : Calendar manager * Streamline logic * Enable recurring sessions through rrule * Implement method to create ics feed for user * Extract a type-safe scheduled duration method on music_session for external and internal use. commit b71ad3a4cdd943eb84748abaa85fec263b9af468 VRFS-3276 : Include calendar manager commit f8eaafd03647613dafec9f9422282f8613d08e9a VRFS-3276 : Calendar Manager - initial checkin * Create ICS events given individual parameters * Create calendar from music session * Also will create ICS “delete” events
2015-07-06 20:34:27 +00:00
duration
end
def pretty_scheduled_start(with_timezone = true, shorter = false)
2014-07-12 02:11:34 +00:00
2014-07-12 01:54:54 +00:00
if scheduled_start && scheduled_duration
start_time = scheduled_start
timezone_display = 'UTC'
utc_offset_display = '00:00'
2014-07-12 01:54:54 +00:00
tz_identifier, tz_display = MusicSession.split_timezone(timezone)
short_tz = 'GMT'
2014-07-12 01:54:54 +00:00
begin
tz = TZInfo::Timezone.get(tz_identifier)
rescue Exception => e
@@log.error("unable to find timezone=#{tz_identifier}, e=#{e}")
end
if tz
begin
2014-07-12 01:54:54 +00:00
start_time = tz.utc_to_local(scheduled_start.utc)
timezone_display = tz.pretty_name
utc_offset_hours = tz.current_period.utc_total_offset / (60*60)
hour = sprintf '%02d', utc_offset_hours.abs
minutes = sprintf '%02d', ((tz.current_period.utc_total_offset.abs % 3600) / 3600) * 60
utc_offset_display = "#{utc_offset_hours < 0 ? '-' : ' '}#{hour}:#{minutes}"
short_tz = start_time.strftime("%Z")
if short_tz == 'UTC'
short_tz = 'GMT'
end
rescue Exception => e
2014-07-12 01:54:54 +00:00
@@log.error("unable to convert #{scheduled_start} to #{tz}, e=#{e}")
puts "unable to convert #{e}"
end
end
2014-07-12 01:54:54 +00:00
2016-05-18 03:11:20 +00:00
Merge feature/calendaring branch: commit 8023d6481cbadd52e58b9a4342ac7636ce1807e3 VRFS-3276 : Hook calendar creation into user controller API. Add test to verify. commit 3a35002a46f870e2c490b88b3187e0b1569494fd VRFS-3276 : Calendar cleanup job * Add cleanup method to calendar manager * Create a daily job. * Add calendar cleanup to that job. * Add CRON entry * Daily job/ calendar cleanup test cases * Fix calendar manager spec for new required attribute commit 3ff5910f1f019ae8bcb5afe72a31f1d38bb7d7a3 VRFS-3276 : Add a delete-calendar directive when RSVP is canceled. VRFS-3276 : Include path to partial. This fails depending on the method used to start the web server. commit d2441cbf57e50895ac3b40534873c5d529cb3c4f VRFS-3276 : Test new calendar features. Use icalendar gem in test mode only to more deeply verify calendar in strict mode. commit 9ac272a0fb1e58d8cf9f02e7a0e04caada41f659 VRFS-3276 : Calendar manager updates to include manual calendars. Some refactoring to keep common stuff in one place. commit b5d0c758f0dcae41a5f24635e9da9ce6eda56670 VRFS-3276 : Schema, model updates and new calendar model. commit 20472b6b26c88c04edb9bc698e0c06c549e12eb5 VRFS-3276 : Change initial submit behavior of RSVP dialog to display calendar info. The user can then close the dialog after this prompt. commit 77c99103d0221f20ea342169821b90fa987ecf93 VRFS-3276 : Calendar feed markup and styling. Included as partial. commit e632f48600ae23b5f742773310b2a4ac16ae4ee8 VRFS-3276 : Routes and controller implementation of user calendar ICS feed, which uses calendar manager. commit 21fd80a188eae771a65333566b804ade795a1e8c VRFS-3276 : Initial tests for calendar manager commit 92a2524c65abf7b540f9d50049a1b760a5a9927f VRFS-3276 : Calendar manager * Streamline logic * Enable recurring sessions through rrule * Implement method to create ics feed for user * Extract a type-safe scheduled duration method on music_session for external and internal use. commit b71ad3a4cdd943eb84748abaa85fec263b9af468 VRFS-3276 : Include calendar manager commit f8eaafd03647613dafec9f9422282f8613d08e9a VRFS-3276 : Calendar Manager - initial checkin * Create ICS events given individual parameters * Create calendar from music session * Also will create ICS “delete” events
2015-07-06 20:34:27 +00:00
duration = safe_scheduled_duration
end_time = start_time + duration
if with_timezone
if shorter
2016-05-18 03:11:20 +00:00
#"#{start_time.strftime("%a, %b %e %Y")}, #{start_time.strftime("%l:%M").strip}-#{end_time.strftime("%l:%M %p").strip} (#{short_tz}#{utc_offset_display})"
"#{start_time.strftime("%a, %b %e %Y")}, #{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").strip}-#{end_time.strftime("%l:%M %p").strip} #{timezone_display}"
end
else
2017-02-13 02:49:33 +00:00
"#{start_time.strftime("%A, %B %e")} - #{end_time.strftime("%l:%M%P").strip}"
end
else
"Date and time TBD"
end
end
def self.purgeable_sessions
sessions = []
sql =<<SQL
(started_at IS NULL AND
(created_at <= NOW() - interval '#{UNSTARTED_INTERVAL_DAYS_PURGE} days' OR
scheduled_start <= NOW() - interval '#{UNSTARTED_INTERVAL_DAYS_PURGE} days'))
OR
(recurring_mode = '#{NO_RECURRING}' AND
scheduled_start <= NOW() - interval '#{UNSTARTED_INTERVAL_DAYS_PURGE} days')
OR
(recurring_mode = '#{RECURRING_WEEKLY}' AND
scheduled_start <= NOW() - interval '#{UNSTARTED_INTERVAL_DAYS_PURGE_RECUR} days')
SQL
self.where("lesson_session_id is NULL").where("started_at IS NULL").where(sql).find_each do |ms|
block_given? ? yield(ms) : sessions << ms
end
sessions
end
private
2014-05-06 13:34:38 +00:00
def generate_share_token
token = loop do
token = SecureRandom.urlsafe_base64(SHARE_TOKEN_LENGTH, false)
token = remove_non_alpha_num(token)
token.upcase!
break token unless ShareToken.exists?(token: token)
end
self.share_token = ShareToken.new
self.share_token.token = token
self.share_token.shareable_type = "session"
end
2014-05-06 13:34:38 +00:00
def creator_is_musician
unless creator && creator.musician?
errors.add(:creator, ValidationMessages::MUST_BE_A_MUSICIAN)
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