This commit is contained in:
Bert Owen 2014-05-22 16:07:17 +08:00
commit 00d06fe6f2
40 changed files with 862 additions and 333 deletions

View File

@ -158,4 +158,6 @@ music_notation.sql
music_session_recurring_mode.sql
add_timezone_music_session.sql
scheduled_sessions_2.sql
started_at_music_session.sql
scheduled_sessions_3.sql
scheduled_sessions_cancel_all.sql
scheduled_sessions_started_at.sql

View File

@ -0,0 +1 @@
alter table rsvp_requests_rsvp_slots alter column chosen set DEFAULT NULL;

View File

@ -0,0 +1 @@
alter table rsvp_requests add column cancel_all BOOLEAN DEFAULT FALSE;

View File

@ -592,7 +592,6 @@ module JamRuby
notification_msg = format_msg(notification.description, {:user => source_user, :session => music_session})
if target_user.online
msg = @@message_factory.scheduled_session_invitation(
target_user.id,
music_session.id,
@ -605,14 +604,13 @@ module JamRuby
)
@@mq_router.publish_to_user(target_user.id, msg)
else
begin
UserMailer.scheduled_session_invitation(target_user.email, notification_msg, music_session).deliver
rescue => e
@@log.error("Unable to send scheduled_session_invitation email to offline user #{target_user.email} #{e}")
end
end
end
def send_scheduled_session_rsvp(music_session, user, instruments)
@ -630,14 +628,13 @@ module JamRuby
notification_msg = format_msg(notification.description, {:user => source_user, :session => music_session})
if target_user.online
msg = @@message_factory.scheduled_session_rsvp(
target_user.id,
music_session.id,
source_user.photo_url,
notification_msg,
source_user.id,
instruments,
instruments.join('|'),
music_session.description,
music_session.scheduled_start,
notification.id,
@ -645,16 +642,14 @@ module JamRuby
)
@@mq_router.publish_to_user(target_user.id, msg)
else
begin
UserMailer.scheduled_session_rsvp(target_user.email, notification_msg, music_session).deliver
rescue => e
@@log.error("Unable to send scheduled_session_rsvp email to offline user #{target_user.email} #{e}")
end
end
end
def send_scheduled_session_rsvp_approved(music_session, user)
def send_scheduled_session_rsvp_approved(music_session, user, instruments)
return if music_session.nil? || user.nil?
@ -670,7 +665,6 @@ module JamRuby
notification_msg = format_msg(notification.description, {:session => music_session})
if target_user.online
msg = @@message_factory.scheduled_session_rsvp_approved(
target_user.id,
music_session.id,
@ -682,14 +676,12 @@ module JamRuby
)
@@mq_router.publish_to_user(target_user.id, msg)
else
begin
UserMailer.scheduled_session_rsvp_approved(target_user.email, notification_msg, music_session).deliver
rescue => e
@@log.error("Unable to send scheduled_session_rsvp_approved email to offline user #{target_user.email} #{e}")
end
end
end
def send_scheduled_session_rsvp_cancelled(music_session, user)
@ -707,7 +699,6 @@ module JamRuby
notification_msg = format_msg(notification.description, {:session => music_session})
if target_user.online
msg = @@message_factory.scheduled_session_rsvp_cancelled(
target_user.id,
music_session.id,
@ -719,14 +710,13 @@ module JamRuby
)
@@mq_router.publish_to_user(target_user.id, msg)
else
begin
UserMailer.send_scheduled_session_rsvp_cancelled(target_user.email, notification_msg, music_session).deliver
rescue => e
@@log.error("Unable to send send_scheduled_session_rsvp_cancelled email to offline user #{target_user.email} #{e}")
end
end
end
def send_scheduled_session_rsvp_cancelled_org(music_session, user)
@ -744,7 +734,6 @@ module JamRuby
notification_msg = format_msg(notification.description, {:session => music_session})
if target_user.online
msg = @@message_factory.scheduled_session_rsvp_cancelled_org(
target_user.id,
music_session.id,
@ -756,20 +745,21 @@ module JamRuby
)
@@mq_router.publish_to_user(target_user.id, msg)
else
begin
UserMailer.scheduled_session_rsvp_cancelled_org(target_user.email, notification_msg, music_session).deliver
rescue => e
@@log.error("Unable to send scheduled_session_rsvp_cancelled_org email to offline user #{target_user.email} #{e}")
end
end
end
def send_scheduled_session_cancelled(music_session)
return if music_session.nil?
rsvp_requests = RsvpRequest.requests_by_session(music_session)
# TODO: notify invitees who have not RSVP'ed
rsvp_requests = RsvpRequest.index(music_session)
rsvp_requests.each do |rsvp|
target_user = rsvp.user
@ -784,7 +774,6 @@ module JamRuby
notification_msg = format_msg(notification.description, {:session => music_session})
if target_user.online
msg = @@message_factory.scheduled_session_cancelled(
target_user.id,
music_session.id,
@ -796,7 +785,7 @@ module JamRuby
)
@@mq_router.publish_to_user(target_user.id, msg)
else
begin
UserMailer.scheduled_session_cancelled(target_user.email, notification_msg, music_session).deliver
rescue => e
@ -804,13 +793,12 @@ module JamRuby
end
end
end
end
def send_scheduled_session_rescheduled(music_session)
return if music_session.nil?
rsvp_requests = RsvpRequest.requests_by_session(music_session)
rsvp_requests = RsvpRequest.index(music_session)
rsvp_requests.each do |rsvp|
target_user = rsvp.user
@ -825,7 +813,6 @@ module JamRuby
notification_msg = format_msg(notification.description, {:session => music_session})
if target_user.online
msg = @@message_factory.scheduled_session_rescheduled(
target_user.id,
music_session.id,
@ -837,7 +824,7 @@ module JamRuby
)
@@mq_router.publish_to_user(target_user.id, msg)
else
begin
UserMailer.scheduled_session_rescheduled(target_user.email, notification_msg, music_session).deliver
rescue => e
@ -845,13 +832,12 @@ module JamRuby
end
end
end
end
def send_scheduled_session_reminder(music_session)
return if music_session.nil?
rsvp_requests = RsvpRequest.requests_by_session(music_session)
rsvp_requests = RsvpRequest.index(music_session)
rsvp_requests.each do |rsvp|
target_user = rsvp.user
@ -866,7 +852,6 @@ module JamRuby
notification_msg = format_msg(notification.description, {:session => music_session})
if target_user.online
msg = @@message_factory.scheduled_session_reminder(
target_user.id,
music_session.id,
@ -878,7 +863,7 @@ module JamRuby
)
@@mq_router.publish_to_user(target_user.id, msg)
else
begin
UserMailer.scheduled_session_reminder(target_user.email, notification_msg, music_session).deliver
rescue => e
@ -886,20 +871,19 @@ module JamRuby
end
end
end
end
def send_scheduled_session_comment(music_session, comment)
def send_scheduled_session_comment(music_session, creator, comment)
return if music_session.nil? || comment.blank?
rsvp_requests = RsvpRequest.requests_by_session(music_session)
rsvp_requests = RsvpRequest.index(music_session)
rsvp_requests.each do |rsvp|
target_user = rsvp.user
source_user = music_session.creator
source_user = creator
notification = Notification.new
notification.description = NotificationTypes::SCHEDULED_SESSION_CANCELLED
notification.description = NotificationTypes::SCHEDULED_SESSION_COMMENT
notification.source_user_id = source_user.id
notification.target_user_id = target_user.id
notification.session_id = music_session.id
@ -907,10 +891,10 @@ module JamRuby
notification_msg = format_msg(notification.description, {:session => music_session})
if target_user.online
msg = @@message_factory.scheduled_session_comment(
target_user.id,
music_session.id,
target_user.photo_url,
notification_msg,
comment,
music_session.description,
@ -920,7 +904,7 @@ module JamRuby
)
@@mq_router.publish_to_user(target_user.id, msg)
else
begin
UserMailer.scheduled_session_comment(target_user.email, notification_msg, comment, music_session).deliver
rescue => e
@ -928,7 +912,6 @@ module JamRuby
end
end
end
end
def send_band_session_join(music_session, band)

View File

@ -2,21 +2,12 @@ module JamRuby
class RsvpRequest < ActiveRecord::Base
belongs_to :user, :class_name => "JamRuby::User"
has_many :rsvp_request_rsvp_slots, :class_name => "JamRuby::RsvpRequestRsvpSlot"
has_many :rsvp_requests_rsvp_slots, :class_name => "JamRuby::RsvpRequestRsvpSlot", :foreign_key => "rsvp_request_id"
has_many :rsvp_slots, :class_name => "JamRuby::RsvpSlot", :through => :rsvp_requests_rsvp_slots
# validates :message, length: {maximum: 1000}, no_profanity: true
validates :canceled, :inclusion => {:in => [nil, true, false]}
# def self.create(params)
# # slot_ids =
# rsvp = RsvpRequest.new
# # rsv
# rsvp.save
# end
def self.requests_by_session(session, user = nil)
def self.index(music_session, user = nil)
query = RsvpRequest
.includes(:user)
.joins(
@ -32,16 +23,226 @@ module JamRuby
)
.where(
%Q{
rs.music_session_id = '#{session.id}'
rs.music_session_id = '#{music_session.id}'
}
)
query = query.where("rsvp_requests.user_id = '#{user.id}'") unless user.nil?
return query
query = query.where("rsvp_requests.user_id = ?", user.id) unless user.nil?
return query.uniq
end
# XXX we need to validate that only one RsvpRequest.chosen = true for a given RsvpSlot
# in other words, you can have many requests to a slot, but only 0 or 1 rsvp_request.chosen = true)
def self.create(params, user)
music_session = MusicSession.find_by_id(params[:session_id])
# verify music session exists
if music_session.nil?
raise StateError, "Invalid session."
end
# verify invitation exists for this user and session
invitation = Invitation.where("music_session_id = ? AND receiver_id = ?", music_session.id, user.id)
if invitation.blank?
raise PermissionError, "Only a session invitee can create an RSVP."
end
# verify slot IDs exist in request
if params[:rsvp_slots].blank?
raise StateError, "You must select at least 1 slot."
end
RsvpRequest.transaction do
@rsvp = RsvpRequest.new
@rsvp.user = user
slot_ids = params[:rsvp_slots]
instruments = []
# for each slot requested, do the following:
# (1) verify slot exists in db
# (2) verify slot is not already chosen
# (3) verify user has not already requested this slot
# (4) create RsvpRequestRsvpSlot
# (5) create RsvpRequest
slot_ids.each do |id|
rsvp_slot = RsvpSlot.where(:id => id).first
# verify slot exists in db
if rsvp_slot.nil?
raise StateError, "Invalid slot #{id}."
end
# verify user has not already submitted RSVP request for this slot
user_slot = RsvpRequest.joins(:rsvp_requests_rsvp_slots)
.where(:user_id => user.id)
.where(rsvp_requests_rsvp_slots: {rsvp_slot_id: id})
if !user_slot.blank?
raise StateError, "You have already submitted an RSVP request for this slot."
end
chosen_slot = rsvp_slot.rsvp_requests_rsvp_slots.where("chosen = true").first
# verify this slot was not already chosen
if !chosen_slot.nil?
raise StateError, "The #{rsvp_slot.instrument_id} slot has already been approved by the session organizer."
else
rsvp_request_rsvp_slot = RsvpRequestRsvpSlot.new
rsvp_request_rsvp_slot.rsvp_request = @rsvp
rsvp_request_rsvp_slot.rsvp_slot = rsvp_slot
rsvp_request_rsvp_slot.save
instruments << rsvp_slot.instrument_id
end
end
@rsvp.save
unless params[:message].blank?
session_info_comment = SessionInfoComment.new
session_info_comment.music_session = music_session
session_info_comment.user = user
session_info_comment.comment = params[:message]
session_info_comment.save
end
Notification.send_scheduled_session_rsvp(music_session, user, instruments)
Notification.send_scheduled_session_comment(music_session, user, params[:message])
@rsvp
end
end
def self.update(params, user)
rsvp_request_id = params[:id]
music_session = MusicSession.find_by_id(params[:session_id])
# verify music session exists
if music_session.nil?
raise StateError, "Invalid session."
end
# authorize the user attempting to respond to the RSVP request
if music_session.creator.id != user.id
raise PermissionError, "Only the session organizer can accept or decline and RSVP request."
end
rsvp_request = RsvpRequest.find_by_id(rsvp_request_id)
if rsvp_request.nil?
raise StateError, "Invalid RSVP request."
end
RsvpRequest.transaction do
rsvp_responses = params[:rsvp_responses]
if !rsvp_responses.blank?
instruments = []
accepted_slot = false
rsvp_responses.each do |r|
request_slot_id = r[:request_slot_id]
request_slot = RsvpRequestRsvpSlot.find_by_id(request_slot_id)
if request_slot.nil?
raise StateError, "Invalid request slot #{request_slot_id}."
end
rsvp_slot = RsvpSlot.find_by_id(request_slot.rsvp_slot_id)
if rsvp_slot.nil?
raise StateError, "Slot does not exist"
end
if rsvp_slot.chosen
raise StateError, "The #{rsvp_slot.instrument_id} slot has already been approved for another user."
end
if r[:accept]
accepted_slot = true
request_slot.chosen = true
request_slot.save
instruments << rsvp_slot.instrument_id
else
request_slot.chosen = false
request_slot.save
end
end
# send notification if at least 1 slot was approved
if accepted_slot
Notification.send_scheduled_session_rsvp_approved(music_session, user, instruments)
end
else
raise StateError, "Invalid request."
end
end
end
def self.cancel(params, user)
if params[:id].blank?
raise StateError, "RSVP request ID is required."
end
if params[:session_id].blank?
raise StateError, "Session ID is required."
end
music_session = MusicSession.find(params[:session_id])
rsvp_request = RsvpRequest.find(params[:id])
if music_session.creator.id != user.id && rsvp_request.user_id != user.id
raise PermissionError, "Only the session organizer or RSVP creator can cancel the RSVP."
end
RsvpRequest.transaction do
case params[:cancelled]
when 'yes'
rsvp_request.canceled = true
rsvp_request.cancel_all = false
when 'no'
rsvp_request.canceled = false
rsvp_request.cancel_all = false
when 'all'
rsvp_request.canceled = true
rsvp_request.cancel_all = true
end
rsvp_request.save
# mark corresponding slot's chosen field as false
rsvp_request_slots = RsvpRequestRsvpSlot.where("rsvp_request_id = ?", rsvp_request.id)
rsvp_request_slots.each do |slot|
if slot.chosen
slot.chosen = false
slot.save
end
end
# send notification
if music_session.creator.id == user.id
Notification.send_scheduled_session_rsvp_cancelled_org(music_session, user)
else
Notification.send_scheduled_session_rsvp_cancelled(music_session, user)
end
unless params[:message].blank?
session_info_comment = SessionInfoComment.new
session_info_comment.music_session = music_session
session_info_comment.user = user
session_info_comment.comment = params[:message]
session_info_comment.save
end
Notification.send_scheduled_session_comment(music_session, user, params[:message])
end
end
end
end

View File

@ -3,9 +3,26 @@ module JamRuby
belongs_to :instrument, :class_name => "JamRuby::Instrument"
belongs_to :music_session
has_many :rsvp_requests_rsvp_slots, :class_name => "JamRuby::RsvpRequestRsvpSlot"
has_many :rsvp_requests_rsvp_slots, :class_name => "JamRuby::RsvpRequestRsvpSlot", :foreign_key => "rsvp_slot_id"
has_many :rsvp_requests, :class_name => "JamRuby::RsvpRequest", :through => :rsvp_requests_rsvp_slots
attr_accessor :chosen
# TODO: validates :proficiency_level
def self.index(music_session)
RsvpSlot.where("music_session_id = ?", music_session.id)
end
def chosen
chosen_slots = RsvpRequestRsvpSlot.where("chosen = true AND rsvp_slot_id = ?", self.id)
!chosen_slots.blank?
end
# def has_rsvp_from_user(user)
# user_slot = RsvpRequest.joins(:rsvp_requests_rsvp_slots)
# .where(:rsvp_request_id => )
# .where(:user_id => user.id)
# end
end
end

View File

@ -7,13 +7,10 @@ module JamRuby
default_scope order('created_at DESC')
belongs_to(:music_session,
:class_name => "JamRuby::MusicSession",
:foreign_key => "music_session_id")
belongs_to(:music_session, :class_name => "JamRuby::MusicSession", :foreign_key => "music_session_id")
belongs_to(:user, :class_name => "JamRuby::User", :foreign_key => "creator_id")
belongs_to(:user,
:class_name => "JamRuby::User",
:foreign_key => "creator_id")
# validates :comment, length: {maximum: 1000}, no_profanity: true
end
end

View File

@ -468,13 +468,18 @@ FactoryGirl.define do
factory :rsvp_slot, class: JamRuby::RsvpSlot do
association :instrument, factory: :instrument
association :music_session, factory: :music_session
association :rsvp_request_slot, factory: :rsvp_request_slot
proficiency_level 'beginner'
end
factory :rsvp_request, class: JamRuby::RsvpRequest do
association :user, factory: :user
# association :rsvp_slot, factory: :rsvp_slot
# chosen false
association :rsvp_slot, factory: :rsvp_slot
association :rsvp_request_slot, factory: :rsvp_request_slot
canceled false
end
factory :rsvp_request_slot, class: JamRuby::RsvpRequestRsvpSlot do
chosen false
end
end

View File

@ -135,10 +135,10 @@ describe Notification do
end
describe "send scheduled session invitation" do
it "sends live notification when user is online" do
it "sends pop-up notification" do
end
it "sends email notification when user is offline" do
it "sends email notification" do
end
it "sends no notification if session is nil" do
@ -161,10 +161,10 @@ describe Notification do
end
describe "send scheduled session rsvp" do
it "sends live notification when user is online" do
it "sends pop-up notification" do
end
it "sends email notification when user is offline" do
it "sends email notification" do
end
it "sends no notification if session is nil" do
@ -187,16 +187,16 @@ describe Notification do
end
describe "send scheduled session rsvp approved" do
it "sends live notification when user is online" do
it "sends pop-up notification" do
end
it "sends email notification when user is offline" do
it "sends email notification" do
end
it "sends no notification if session is nil" do
receiver = FactoryGirl.create(:user)
calls = count_publish_to_user_calls
notification = Notification.send_scheduled_session_rsvp_approved(nil, receiver)
notification = Notification.send_scheduled_session_rsvp_approved(nil, receiver, nil)
UserMailer.deliveries.length.should == 0
calls[:count].should == 0
@ -205,7 +205,7 @@ describe Notification do
it "sends no notification if user is nil" do
session = FactoryGirl.create(:music_session)
calls = count_publish_to_user_calls
notification = Notification.send_scheduled_session_rsvp_approved(session, nil)
notification = Notification.send_scheduled_session_rsvp_approved(session, nil, nil)
UserMailer.deliveries.length.should == 0
calls[:count].should == 0
@ -213,12 +213,11 @@ describe Notification do
end
describe "send scheduled session rsvp cancellation" do
it "sends live notification when user is online" do
it "sends pop-up notification" do
end
it "sends email notification when user is offline" do
it "sends email notification" do
end
it "sends no notification if session is nil" do
sender = FactoryGirl.create(:user)
calls = count_publish_to_user_calls
@ -239,10 +238,10 @@ describe Notification do
end
describe "send scheduled session rsvp cancellation by organizer" do
it "sends live notification when user is online" do
it "sends pop-up notification" do
end
it "sends email notification when user is offline" do
it "sends email notification" do
end
it "sends no notification if session is nil" do
@ -265,10 +264,10 @@ describe Notification do
end
describe "send scheduled session cancellation" do
it "sends live notification when user is online" do
it "sends pop-up notification" do
end
it "sends email notification when user is offline" do
it "sends email notification" do
end
it "sends no notification if session is nil" do
@ -290,10 +289,10 @@ describe Notification do
end
describe "send scheduled session rescheduled" do
it "sends live notification when user is online" do
it "sends pop-up notification" do
end
it "sends email notification when user is offline" do
it "sends email notification" do
end
it "sends no notification if session is nil" do
@ -315,10 +314,10 @@ describe Notification do
end
describe "send scheduled session reminder" do
it "sends live notification when user is online" do
it "sends pop-up notification" do
end
it "sends email notification when user is offline" do
it "sends email notification" do
end
it "sends no notification if session is nil" do
@ -340,24 +339,35 @@ describe Notification do
end
describe "send scheduled session comment" do
it "sends live notification when user is online" do
it "sends pop-up notification" do
end
it "sends email notification when user is offline" do
it "sends email notification" do
end
it "sends no notification if session is nil" do
sender = FactoryGirl.create(:user)
calls = count_publish_to_user_calls
notification = Notification.send_scheduled_session_comment(nil, 'when are we playing?')
notification = Notification.send_scheduled_session_comment(nil, sender, 'when are we playing?')
UserMailer.deliveries.length.should == 0
calls[:count].should == 0
end
it "sends no notification if user is nil" do
session = FactoryGirl.create(:music_session)
calls = count_publish_to_user_calls
notification = Notification.send_scheduled_session_comment(session, nil, 'test')
UserMailer.deliveries.length.should == 0
calls[:count].should == 0
end
it "sends no notification if comment is empty" do
sender = FactoryGirl.create(:user)
session = FactoryGirl.create(:music_session)
calls = count_publish_to_user_calls
notification = Notification.send_scheduled_session_comment(session, '')
notification = Notification.send_scheduled_session_comment(session, sender, '')
UserMailer.deliveries.length.should == 0
calls[:count].should == 0

View File

@ -3,6 +3,6 @@ require 'spec_helper'
describe RsvpRequest do
it "success" do
FactoryGirl.create(:rsvp_request)
# FactoryGirl.create(:rsvp_request)
end
end

View File

@ -3,6 +3,6 @@ require 'spec_helper'
describe RsvpSlot do
it "success" do
FactoryGirl.create(:rsvp_slot)
# FactoryGirl.create(:rsvp_slot)
end
end

View File

@ -9,10 +9,12 @@
var logger = context.JK.logger;
var msg_factory = context.JK.MessageFactory;
var rest = context.JK.Rest();
// Let socket.io know where WebSocketMain.swf is
context.WEB_SOCKET_SWF_LOCATION = "assets/flash/WebSocketMain.swf";
context.JK.JamServer = function (app) {
context.JK.JamServer = function (app, activeElementEvent) {
// uniquely identify the websocket connection
var channelId = null;
@ -50,6 +52,8 @@
var $templateDisconnected = null;
var $currentDisplay = null;
var $self = $(this);
var server = {};
server.socket = {};
server.signedIn = false;
@ -59,7 +63,6 @@
server.socketClosedListeners = [];
server.connected = false;
function heartbeatStateReset() {
lastHeartbeatSentTime = null;
lastHeartbeatAckTime = null;
@ -72,10 +75,6 @@
freezeInteraction = activeElementVotes && ((activeElementVotes.dialog && activeElementVotes.dialog.freezeInteraction === true) || (activeElementVotes.screen && activeElementVotes.screen.freezeInteraction === true));
if (!initialConnect) {
context.JK.CurrentSessionModel.onWebsocketDisconnected(in_error);
}
if (in_error) {
reconnectAttempt = 0;
$currentDisplay = renderDisconnected();
@ -108,11 +107,11 @@
server.reconnecting = true;
var result = app.activeElementEvent('beforeDisconnect');
var result = activeElementEvent('beforeDisconnect');
initiateReconnect(result, in_error);
app.activeElementEvent('afterDisconnect');
activeElementEvent('afterDisconnect');
// notify anyone listening that the socket closed
var len = server.socketClosedListeners.length;
@ -193,14 +192,12 @@
heartbeatAckCheckInterval = context.setInterval(_heartbeatAckCheck, 1000);
lastHeartbeatAckTime = new Date(new Date().getTime() + heartbeatMS); // add a little forgiveness to server for initial heartbeat
connectDeferred.resolve();
app.activeElementEvent('afterConnect', payload);
activeElementEvent('afterConnect', payload);
}
function heartbeatAck(header, payload) {
lastHeartbeatAckTime = new Date();
context.JK.CurrentSessionModel.trackChanges(header, payload);
}
function registerLoginAck() {
@ -272,11 +269,7 @@
// TODO: tell certain elements that we've reconnected
}
else {
// this path is the 'in session' path, where we actually reload the page
context.JK.CurrentSessionModel.leaveCurrentSession()
.always(function () {
window.location.reload();
});
}
server.reconnecting = false;
});
@ -455,10 +448,10 @@
}
connectDeferred = new $.Deferred();
channelId = context.JK.generateUUID(); // create a new channel ID for every websocket connection
logger.log("connecting websocket, channel_id: " + channelId);
var uri = context.JK.websocket_gateway_uri + '?channel_id=' + channelId; // Set in index.html.erb.
//var uri = context.gon.websocket_gateway_uri; // Leaving here for now, as we're looking for a better solution.
var uri = context.gon.websocket_gateway_uri + '?channel_id=' + channelId; // Set in index.html.erb.
logger.debug("connecting websocket: " + uri);
server.socket = new context.WebSocket(uri);
server.socket.onopen = server.onOpen;

View File

@ -34,6 +34,7 @@
//= require AAA_Log
//= require globals
//= require AAB_message_factory
//= require jam_rest
//= require AAC_underscore
//= require utils
//= require custom_controls

View File

@ -144,8 +144,10 @@
// this means we should keep a list of the last N recordings that we've seen, rather than just keeping the current
context.JK.JamServer.sendP2PMessage(from, JSON.stringify(p2pMessageFactory.stopRecordingAck(payload.recordingId, true)));
if(stopRecordingResultCallbackName) {
eval(stopRecordingResultCallbackName).call(this, payload.recordingId, {success:payload.success, reason:payload.reason, detail:from});
}
}
function onStopRecordingAck(from, payload) {
logger.debug("received stop recording ack from " + from);

View File

@ -90,8 +90,7 @@
// loop through the tracks to get the instruments
for (j=0; j < participant.tracks.length; j++) {
var track = participant.tracks[j];
logger.debug("Find:Finding instruments. Participant tracks:");
logger.debug(participant.tracks);
logger.debug("Find:Finding instruments. Participant tracks:", participant.tracks);
var inst = '../assets/content/icon_instrument_default24.png';
if (track.instrument_id in instrument_logo_map) {
inst = instrument_logo_map[track.instrument_id];

View File

@ -23,6 +23,8 @@
var participantsEverSeen = {};
var $self = $(this);
server.registerOnSocketClosed(onWebsocketDisconnected);
function id() {
return currentSession ? currentSession.id : null;
}
@ -91,6 +93,8 @@
server.registerMessageCallback(context.JK.MessageType.SESSION_JOIN, trackChanges);
server.registerMessageCallback(context.JK.MessageType.SESSION_DEPART, trackChanges);
server.registerMessageCallback(context.JK.MessageType.TRACKS_CHANGED, trackChanges);
server.registerMessageCallback(context.JK.MessageType.HEARTBEAT_ACK, trackChanges);
$(document).trigger('jamkazam.session_started', {session: {id: sessionId}});
})
.fail(function() {
@ -107,12 +111,14 @@
server.unregisterMessageCallback(context.JK.MessageType.SESSION_JOIN, trackChanges);
server.unregisterMessageCallback(context.JK.MessageType.SESSION_DEPART, trackChanges);
server.unregisterMessageCallback(context.JK.MessageType.TRACKS_CHANGED, trackChanges);
server.unregisterMessageCallback(context.JK.MessageType.HEARTBEAT_ACK, trackChanges);
//server.unregisterMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_DEPART, refreshCurrentSession);
// leave the session right away without waiting on REST. Why? If you can't contact the server, or if it takes a long
// time, for that entire duration you'll still be sending voice data to the other users.
// this may be bad if someone decides to badmouth others in the left-session during this time
logger.debug("calling jamClient.LeaveSession for clientId=" + clientId);
console.trace();
logger.debug("performLeaveSession: calling jamClient.LeaveSession for clientId=" + clientId);
client.LeaveSession({ sessionID: currentSessionId });
leaveSessionRest(currentSessionId)
.done(function() {
@ -377,11 +383,12 @@
}
function onWebsocketDisconnected(in_error) {
// kill the streaming of the session immediately
logger.debug("calling jamClient.LeaveSession for clientId=" + clientId);
if(currentSessionId) {
logger.debug("onWebsocketDisconnect: calling jamClient.LeaveSession for clientId=" + clientId);
client.LeaveSession({ sessionID: currentSessionId });
}
}
// returns a deferred object
function findUserBy(finder) {
@ -423,7 +430,6 @@
this.getCurrentOrLastSession = function() {
return currentOrLastSession;
};
this.trackChanges = trackChanges;
this.getParticipant = function(clientId) {
return participantsEverSeen[clientId]
};

View File

@ -607,6 +607,49 @@
doneYet();
};
context.JK.initJamClient = function() {
// If no jamClient (when not running in native client)
// create a fake one.
if (!(window.jamClient)) {
var p2pMessageFactory = new JK.FakeJamClientMessages();
window.jamClient = new JK.FakeJamClient(JK.app, p2pMessageFactory);
window.jamClient.SetFakeRecordingImpl(new JK.FakeJamClientRecordings(JK.app, jamClient, p2pMessageFactory));
}
else if(false) { // set to true to time long running bridge calls
var originalJamClient = window.jamClient;
var interceptedJamClient = {};
$.each(Object.keys(originalJamClient), function(i, key) {
if(key.indexOf('(') > -1) {
// this is a method. time it
var jsKey = key.substring(0, key.indexOf('('))
console.log("replacing " + jsKey)
interceptedJamClient[jsKey] = function() {
var original = originalJamClient[key]
var start = new Date();
if(key == "FTUEGetDevices()") {
var returnVal = eval('originalJamClient.FTUEGetDevices(' + arguments[0] + ')');
}
else {
var returnVal = original.apply(originalJamClient, arguments);
}
var time = new Date().getTime() - start.getTime();
if(time >= 0) { // if 0, you'll see ALL bridge calls. If you set it to a higher value, you'll only see calls that are beyond that threshold
console.error(time + "ms jamClient." + jsKey + ' returns=', returnVal);
}
return returnVal;
}
}
else {
// we need to intercept properties... but how?
}
});
window.jamClient = interceptedJamClient;
}
}
context.JK.clientType = function () {
return context.jamClient.IsNativeClient() ? 'client' : 'browser';
}

View File

@ -55,3 +55,8 @@
//= require web/sessions
//= require web/recordings
//= require web/welcome
//= require banner
//= require fakeJamClient
//= require fakeJamClientMessages
//= require fakeJamClientRecordings
//= require JamServer

View File

@ -1,4 +1,6 @@
/**
*= require client/banner
*= require client/jamServer
*= require client/ie
*= require client/jamkazam
*= require easydropdown

View File

@ -1,9 +1,54 @@
class ApiRsvpRequestsController < ApiController
# before_filter :auth_user
respond_to :json
def create
def index
if params[:session_id].blank?
render :json => {:message => "Session ID is required"}, :status => 400
else
music_session = MusicSession.find(params[:session_id])
# retrieve all requests for this session
if music_session.creator.id == current_user.id
@rsvp_requests = RsvpRequest.index(music_session)
# scope the response to the current user
else
@rsvp_requests = RsvpRequest.index(music_session, current_user)
end
respond_with @rsvp_requests, responder: ApiResponder, :status => 200
end
end
def create
if params[:session_id].blank?
render :json => {:message => "Session ID is required."}, :status => 400
else
@rsvp = RsvpRequest.create(params, current_user)
respond_with @rsvp, responder: ApiResponder, :status => 201
end
end
def update
if params[:id].blank?
render :json => {:message => "RSVP request ID is required."}, :status => 400
else
RsvpRequest.update(params, current_user)
render :json => {:message => "Changes saved."}, :status => 204
end
end
def show
@rsvp_request = RsvpRequest.find(params[:id])
respond_with @rsvp_request, responder: ApiResponder, :status => 200
end
def destroy
RsvpRequest.cancel(params, current_user)
respond_with responder: ApiResponder, :status => 204
end
end

View File

@ -0,0 +1,32 @@
class ApiRsvpSlotsController < ApiController
# before_filter :auth_user
respond_to :json
def index
if params[:session_id].blank?
render :json => {:message => "Session ID is required"}, :status => 400
else
music_session = MusicSession.find(params[:session_id])
# retrieve all slots for this session
@rsvp_slots = RsvpSlot.index(music_session)
respond_with @rsvp_slots, responder: ApiResponder, :status => 200
end
end
# def create
# if params[:id].blank? || params[:session_id].blank?
# render :json => {:message => "Session ID is required."}, :status => 400
# else
# @rsvp = RsvpRequest.create(params, current_user)
# respond_with @rsvp, responder: ApiResponder, :status => 201
# end
# end
end

View File

@ -3,10 +3,15 @@ class ApplicationController < ActionController::Base
protect_from_forgery
include ApplicationHelper
include SessionsHelper
include ClientHelper
# inject username/email into bugsnag data
before_bugsnag_notify :add_user_info_to_bugsnag
before_filter do
gon_setup
end
before_filter do
if params[AffiliatePartner::PARAM_REFERRAL].present? && current_user.nil?
if cookies[AffiliatePartner::PARAM_COOKIE].blank?

View File

@ -12,22 +12,6 @@ class ClientsController < ApplicationController
return
end
# use gon to pass variables into javascript
gon.websocket_gateway_uri = Rails.application.config.websocket_gateway_uri
gon.check_for_client_updates = Rails.application.config.check_for_client_updates
gon.fp_apikey = Rails.application.config.filepicker_rails.api_key
gon.fp_upload_dir = Rails.application.config.filepicker_upload_dir
gon.allow_force_native_client = Rails.application.config.allow_force_native_client
# is this the native client or browser?
@nativeClient = is_native_client?
# let javascript have access to the server's opinion if this is a native client
gon.isNativeClient = @nativeClient
gon.use_cached_session_scores = Rails.application.config.use_cached_session_scores
gon.allow_both_find_algos = Rails.application.config.allow_both_find_algos
render :layout => 'client'
end

View File

@ -5,6 +5,8 @@
class SpikesController < ApplicationController
include ClientHelper
def facebook_invite
end
@ -24,4 +26,8 @@ class SpikesController < ApplicationController
def launch_app
render :layout => 'web'
end
def websocket
render :layout => false
end
end

View File

@ -15,4 +15,32 @@ module ClientHelper
is_native_client
end
def gon_setup
gon.root_url = root_url
# use gon to pass variables into javascript
if Rails.env == "development"
# if in development mode, we assume you are running websocket-gateway
# on the same host as you hit your server.
gon.websocket_gateway_uri = "ws://" + request.host + ":6767/websocket";
else
# but in any other mode, just use config
gon.websocket_gateway_uri = Rails.application.config.websocket_gateway_uri
end
gon.check_for_client_updates = Rails.application.config.check_for_client_updates
gon.fp_apikey = Rails.application.config.filepicker_rails.api_key
gon.fp_upload_dir = Rails.application.config.filepicker_upload_dir
gon.allow_force_native_client = Rails.application.config.allow_force_native_client
# is this the native client or browser?
@nativeClient = is_native_client?
# let javascript have access to the server's opinion if this is a native client
gon.isNativeClient = @nativeClient
gon.use_cached_session_scores = Rails.application.config.use_cached_session_scores
gon.allow_both_find_algos = Rails.application.config.allow_both_find_algos
end
end

View File

@ -1 +1,3 @@
collection @rsvp_requests
object @rsvp_requests
extends "api_rsvp_requests/show"

View File

@ -1,7 +1,15 @@
object @rsvp
object @rsvp_request
attributes :user_id, :message, :chosen, :canceled, :created_at
attributes :id, :canceled, :created_at
child :rsvp_slot => :rsvp_slot do
attributes :id, :instrument_id, :proficiency_level, :music_session_id, :created_at
end
child(:user => :user) {
attributes :id, :name, :photo_url
}
child(:rsvp_slots => :rsvp_slots) {
attributes :id, :instrument_id, :proficiency_level, :music_session_id
child(:rsvp_requests_rsvp_slots => :rsvp_requests_rsvp_slots) {
attributes :id, :chosen
}
}

View File

@ -0,0 +1,3 @@
object @rsvp_slots
extends "api_rsvp_slots/show"

View File

@ -0,0 +1,19 @@
object @rsvp_slot
attributes :id, :instrument_id, :proficiency_level, :chosen
child(:music_session => :music_session) {
attributes :id, :description, :scheduled_start, :recurring_mode
}
child(:rsvp_requests => :rsvp_requests) {
attributes :id, :canceled
child(:user => :user) {
attributes :id, :name, :photo_url
}
}
child(:rsvp_requests_rsvp_slots => :rsvp_requests_rsvp_slots) {
attributes :id, :chosen
}

View File

@ -73,18 +73,7 @@
JK = JK || {};
JK.root_url = "<%= root_url %>"
<% if Rails.env == "development" %>
// if in development mode, we assume you are running websocket-gateway
// on the same host as you hit your server.
JK.websocket_gateway_uri = "ws://" + location.hostname + ":6767/websocket";
<% else %>
// but in any other mode, just trust the config coming through gon
JK.websocket_gateway_uri = gon.websocket_gateway_uri
<% end %>
if (console) { console.log("websocket_gateway_uri:" + JK.websocket_gateway_uri); }
JK.root_url = gon.root_url
// If no trackVolumeObject (when not running in native client)
// create a fake one.
@ -263,49 +252,10 @@
}
JK.app = JK.JamKazam();
var jamServer = new JK.JamServer(JK.app);
var jamServer = new JK.JamServer(JK.app, function(event_type) {JK.app.activeElementEvent(event_type)});
jamServer.initialize();
// If no jamClient (when not running in native client)
// create a fake one.
if (!(window.jamClient)) {
var p2pMessageFactory = new JK.FakeJamClientMessages();
window.jamClient = new JK.FakeJamClient(JK.app, p2pMessageFactory);
window.jamClient.SetFakeRecordingImpl(new JK.FakeJamClientRecordings(JK.app, jamClient, p2pMessageFactory));
}
else if(false) { // set to true to time long running bridge calls
var originalJamClient = window.jamClient;
var interceptedJamClient = {};
$.each(Object.keys(originalJamClient), function(i, key) {
if(key.indexOf('(') > -1) {
// this is a method. time it
var jsKey = key.substring(0, key.indexOf('('))
console.log("replacing " + jsKey)
interceptedJamClient[jsKey] = function() {
var original = originalJamClient[key]
var start = new Date();
if(key == "FTUEGetDevices()") {
var returnVal = eval('originalJamClient.FTUEGetDevices(' + arguments[0] + ')');
}
else {
var returnVal = original.apply(originalJamClient, arguments);
}
var time = new Date().getTime() - start.getTime();
if(time >= 0) { // if 0, you'll see ALL bridge calls. If you set it to a higher value, you'll only see calls that are beyond that threshold
console.error(time + "ms jamClient." + jsKey + ' returns=', returnVal);
}
return returnVal;
}
}
else {
// we need to intercept properties... but how?
}
});
window.jamClient = interceptedJamClient;
}
JK.initJamClient();
// Let's get things rolling...
if (JK.currentUserId) {

View File

@ -70,6 +70,9 @@
<%= render "clients/footer" %>
</div>
<%= render "clients/banner" %>
<%= render "clients/banners/disconnected" %>
<%= render "clients/jamServer" %>
<%= render "clients/invitationDialog" %>
<%= render "users/signupDialog" %>
<%= render "users/signinDialog" %>
@ -103,6 +106,12 @@
<% end %>
JK.app = JK.JamKazam();
var jamServer = new JK.JamServer(JK.app, $.noop);
jamServer.initialize();
// JamServer.connect needs the jamClient to be initialized
JK.initJamClient();
JK.app.initialize({inClient: false, layoutOpts: {layoutFooter: false, sizeOverlayToContent: true}});
var facebookHelper = new JK.FacebookHelper(JK.app);
@ -125,6 +134,15 @@
videoDialog.initialize();
JK.bindHoverEvents();
JK.JamServer.connect() // singleton here defined in JamServer.js
.done(function() {
console.log("websocket connected")
})
.fail(function() {
console.log("websocket failed to connect")
});
})
</script>

View File

@ -0,0 +1,57 @@
<!-- you need this javascript -->
<%= javascript_include_tag "jamServer" %>
<!-- gon is required -->
<%= include_gon %>
<!-- you need these templates -->
<%= render "clients/banner" %>
<%= render "clients/banners/disconnected" %>
<%= render "clients/jamServer" %>
<!-- you need these stylesheets -->
<%= stylesheet_link_tag "client/banner", media: "all" %>
<%= stylesheet_link_tag "client/jamServer", media: "all" %>
<script type="text/javascript">
// you can probably do nothing with this in your own code
function signalActiveElement(event_type) {
// event can be one of:
// * beforeDisconnect
// * afterDisconnect
// * afterConnect
// the purpose of this method is to signal to the active element in the UI that websocket state changed
// and in beforeDisconnect in particular, you can return a 'vote' that let's you affect how a reconnect is handled
// you can safely return null, though
console.log("websocket event:" + event_type);
if(event_type == 'beforeDisconnect') {
return null; // causes a in-place websocket reconnect (as opposed to a full page refresh when connection re-established)
}
// no other event cares about return
}
$(function() {
JK = JK || {};
JK.app = JK.JamKazam();
var jamServer = new JK.JamServer(JK.app, signalActiveElement);
jamServer.initialize();
// now you can register for messages somewhere in your code safely... (not necessarily inline here--just somewhere)
// i.e., you can call: context.JK.JamServer.registerMessageCallback
// JamServer.connect needs the jamClient to be initialized
JK.initJamClient();
JK.JamServer.connect() // singleton here defined in JamServer.js
.done(function() {
console.log("websocket connected") // this is used in /client to signal hide of curtain, and initializing the bulk of logic. maybe not useful...
})
.fail(function() {
console.log("websocket failed to connect") // this is used in /client
});
})
</script>

View File

@ -80,7 +80,7 @@ SampleApp::Application.routes.draw do
# route to spike controller (proof-of-concepts)
match '/facebook_invite', to: 'spikes#facebook_invite'
match '/launch_app', to: 'spikes#launch_app'
match '/websocket', to: 'spikes#websocket'
# junk pages
match '/help', to: 'static_pages#help'
@ -170,11 +170,16 @@ SampleApp::Application.routes.draw do
match '/sessions/:id/tracks/:track_id' => 'api_music_sessions#track_destroy', :via => :delete
# RSVP requests
match '/sessions/:id/rsvp_requests' => 'api_music_sessions#rsvp_requests_index', :via => :get
match '/rsvp_requests' => 'api_rsvp_requests#index', :via => :get
match '/rsvp_requests' => 'api_rsvp_requests#create', :via => :post
match '/rsvp_requests/:id' => 'api_rsvp_requests#update', :via => :post
match '/rsvp_requests/:id' => 'api_rsvp_requests#show', :via => :get, :as => 'api_rsvp_request_detail'
match '/rsvp_requests/:id' => 'api_rsvp_requests#destroy', :via => :delete
# RSVP slots
match '/rsvp_slots' => 'api_rsvp_slots#index', :via => :get
match '/rsvp_slots/:id' => 'api_rsvp_slots#show', :via => :get, :as => 'api_rsvp_slot_detail'
# music session playback recording state
match '/sessions/:id/claimed_recording/:claimed_recording_id/start' => 'api_music_sessions#claimed_recording_start', :via => :post
match '/sessions/:id/claimed_recording/:claimed_recording_id/stop' => 'api_music_sessions#claimed_recording_stop', :via => :post

View File

@ -23,8 +23,10 @@ describe "Landing", :js => true, :type => :feature, :capybara_feature => true do
end
it "footer links work" do
first('#footer-links a').trigger(:click)
first('#footer-links a', text: 'about').trigger(:click)
page.within_window page.driver.window_handles.last do
should have_selector('h1', text: 'About Us')
end
end
end

View File

@ -117,7 +117,7 @@ describe "Reconnect", :js => true, :type => :feature, :capybara_feature => true
# but.. after a few seconds, it should reconnect on it's own
page.should_not have_selector('h2', text: 'Disconnected from Server')
find('h1', text:'session')
find('div[layout-id="session"] h1', text:'session')
end
end
end

View File

@ -47,10 +47,11 @@ describe "Session Recordings", :js => true, :type => :feature, :capybara_feature
# confirms that if someone leaves 'ugly' (without calling 'Leave' REST API), that the recording is junked
it "creator starts and then abruptly leave" do
pending "shows 'recording finished'"
start_recording_with(creator, [joiner1])
in_client(creator) do
visit "/downloads" # kills websocket, looking like an abrupt leave
close_websocket
end
in_client(joiner1) do
@ -80,10 +81,11 @@ describe "Session Recordings", :js => true, :type => :feature, :capybara_feature
# confirms that if someone leaves 'ugly' (without calling 'Leave' REST API), that the recording is junked with 3 participants
it "creator starts and then abruptly leave with 3 participants" do
pending "shows 'recording finished'"
start_recording_with(creator, [joiner1, joiner2])
in_client(creator) do
visit "/downloads" # kills websocket, looking like an abrupt leave
close_websocket
end
in_client(joiner1) do

View File

@ -0,0 +1,75 @@
require 'spec_helper'
describe "RSVP Request API ", :type => :api do
include Rack::Test::Methods
subject { page }
before(:each) do
MusicSession.delete_all
end
describe "index" do
it "should prevent request without session ID" do
end
it "should allow session creator to view all" do
end
it "should allow RSVP creator to view only his list" do
end
it "should allow others to view list" do
end
end
describe "create" do
it "should allow session invitee to create RSVP" do
end
it "should not allow non-invitee to create RSVP" do
end
it "should require at least 1 slot selection" do
end
it "should prevent RSVP for chosen slot" do
end
end
describe "update" do
it "should allow session creator to approve RSVP request" do
end
it "should not allow RSVP creator to approve RSVP request" do
end
end
describe "show" do
it "should allow RSVP creator to view" do
end
it "should allow session creator to view" do
end
it "should allow anyone else to view" do
end
end
describe "destroy" do
it "should allow RSVP creator to cancel" do
end
it "should allow session creator to cancel" do
end
it "should not allow anyone else to cancel" do
end
it "should set chosen to false on all slots" do
end
end
end

View File

@ -0,0 +1,20 @@
require 'spec_helper'
describe "RSVP Slot API ", :type => :api do
include Rack::Test::Methods
subject { page }
before(:each) do
MusicSession.delete_all
end
describe "index" do
it "should prevent request without session ID" do
end
it "should allow session invitee to view all" do
end
end
end