diff --git a/db/manifest b/db/manifest index fb64e8498..0fc258a88 100755 --- a/db/manifest +++ b/db/manifest @@ -241,3 +241,4 @@ jam_track_available.sql active_jam_track.sql bpms_on_tap_in.sql jamtracks_job.sql +text_messages.sql diff --git a/db/up/text_messages.sql b/db/up/text_messages.sql new file mode 100644 index 000000000..4e8a10089 --- /dev/null +++ b/db/up/text_messages.sql @@ -0,0 +1,8 @@ +CREATE TABLE text_messages ( + id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(), + source_user_id VARCHAR(64) REFERENCES users(id) ON DELETE CASCADE, + target_user_id VARCHAR(64) REFERENCES users(id) ON DELETE CASCADE, + message TEXT NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); \ No newline at end of file diff --git a/ruby/lib/jam_ruby.rb b/ruby/lib/jam_ruby.rb index d89371770..084ae7d9e 100755 --- a/ruby/lib/jam_ruby.rb +++ b/ruby/lib/jam_ruby.rb @@ -197,6 +197,7 @@ require "jam_ruby/models/jam_company" require "jam_ruby/models/user_sync" require "jam_ruby/models/video_source" require "jam_ruby/models/recorded_video" +require "jam_ruby/models/text_message" require "jam_ruby/jam_tracks_manager" include Jampb diff --git a/ruby/lib/jam_ruby/models/active_music_session.rb b/ruby/lib/jam_ruby/models/active_music_session.rb index 6dd93acc8..c28ae8de2 100644 --- a/ruby/lib/jam_ruby/models/active_music_session.rb +++ b/ruby/lib/jam_ruby/models/active_music_session.rb @@ -290,13 +290,8 @@ module JamRuby } ) - if (offset) - query = query.offset(offset) - end - - if (limit) - query = query.limit(limit) - end + query = query.offset(offset) if offset + query = query.limit(limit) if limit if as_musician query = query.where( diff --git a/ruby/lib/jam_ruby/models/text_message.rb b/ruby/lib/jam_ruby/models/text_message.rb new file mode 100644 index 000000000..64ef56e4c --- /dev/null +++ b/ruby/lib/jam_ruby/models/text_message.rb @@ -0,0 +1,51 @@ +include Devise::Models + +module JamRuby + class TextMessage < ActiveRecord::Base + + self.primary_key = 'id' + + default_scope order('created_at DESC') + + attr_accessible :target_user_id, :source_user_id, :message + + belongs_to :target_user, :class_name => "JamRuby::User", :foreign_key => "target_user_id" + belongs_to :source_user, :class_name => "JamRuby::User", :foreign_key => "source_user_id" + + validates :message, length: {minimum: 1, maximum: 400}, no_profanity: true + + def self.index(target_user_id, source_user_id, options = {}) + offset = options[:offset] + limit = options[:limit] + + # if not specified, default offset to 0 + offset ||= 0 + offset = offset.to_i + + # if not specified, default limit to 10 + limit ||= 10 + limit = limit.to_i + + TextMessage + .offset(offset) + .limit(limit) + .where('(source_user_id = (?) AND target_user_id = (?)) OR (source_user_id = (?) AND target_user_id = (?))', source_user_id, target_user_id, target_user_id, source_user_id) + + end + + def self.create(message, target_user_id, source_user_id) + sanitized_text = Sanitize.fragment(message, elements: HtmlSanitize::SAFE) + + # create new message + tm = TextMessage.new + tm.message = sanitized_text + tm.target_user_id = target_user_id + tm.source_user_id = source_user_id + + # send notification + @notification = Notification.send_text_message(sanitized_text, source_user_id, User.find_by_id(target_user_id)) + + tm + end + end +end \ No newline at end of file diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index a5ea8b827..cb6ff4b41 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -79,6 +79,9 @@ module JamRuby # self.id = followable_id in follows table has_many :followers, :as => :followable, :class_name => "JamRuby::Follow", :dependent => :destroy + # text messages + has_many :text_messages, :class_name => "JamRuby:TextMessage", :foreign_key => "target_user_id" + # notifications has_many :notifications, :class_name => "JamRuby::Notification", :foreign_key => "target_user_id" has_many :inverse_notifications, :through => :notifications, :class_name => "JamRuby::User" @@ -1017,6 +1020,8 @@ module JamRuby user.save + user.errors.add("recaptcha", "verification failed") if recaptcha_failed + if user.errors.any? raise ActiveRecord::Rollback else @@ -1033,16 +1038,9 @@ module JamRuby UserMailer.confirm_email(user, signup_confirm_url.nil? ? nil : (signup_confirm_url + "/" + user.signup_token) ).deliver end end - - if recaptcha_failed - user.errors.add "recaptcha", "verification failed" - raise ActiveRecord::Rollback - end - end - - return user - end + user + end # def signup # this is intended to be development-mode or test-mode only; VRFS-149 # it creates or updates one user per developer, so that we aren't in the business diff --git a/ruby/spec/jam_ruby/models/text_message.spec.rb b/ruby/spec/jam_ruby/models/text_message.spec.rb new file mode 100644 index 000000000..7b612b3ac --- /dev/null +++ b/ruby/spec/jam_ruby/models/text_message.spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +describe TextMessage do + + before do + TextMessage.delete_all + User.delete_all + @target_user = FactoryGirl.create(:user) + @source_user = FactoryGirl.create(:user) + + @msg = TextMessage.new(:target_user_id => @target_user.id, :source_user_id => @source_user.id) + end + + describe "index" do + + it "should retrieve conversation for both users" do + @msg.message = "Test message" + @msg.save! + + messages = TextMessage.index(@target_user.id, @source_user.id) + messages.count.should == 1 + + messages = TextMessage.index(@source_user.id, @target_user.id) + messages.count.should == 1 + end + + it "should page records" do + 11.times do |n| + message = TextMessage.new(:target_user_id => @target_user.id, :source_user_id => @source_user.id) + message.message = "Message #{n}" + message.save! + end + + messages = TextMessage.index(@target_user.id, @source_user.id, {:offset => 0}) + messages.count.should == 10 + + messages = TextMessage.index(@target_user.id, @source_user.id, {:offset => 10}) + messages.count.should == 1 + end + + it "should not allow empty message" do + expect { @msg.save! }.to raise_error(ActiveRecord::RecordInvalid) + end + + end +end \ No newline at end of file diff --git a/web/app/assets/javascripts/accounts_session_detail.js b/web/app/assets/javascripts/accounts_session_detail.js index d75a5ea2e..82c028e52 100644 --- a/web/app/assets/javascripts/accounts_session_detail.js +++ b/web/app/assets/javascripts/accounts_session_detail.js @@ -339,7 +339,14 @@ if ("instrument_list" in pending_rsvp_request && pending_rsvp_request.instrument_list != null) { $.each(pending_rsvp_request.instrument_list, function (index, instrument) { - var instrumentId = context.JK.getInstrumentId(instrument.id); + var instrumentId; + + if (instrument) { + instrumentId = context.JK.getInstrumentId(instrument.id); + } + else { + instrumentId = 'other'; + } var inst = context.JK.getInstrumentIcon24(instrumentId); instrumentLogoHtml += ' '; instrumentDesc.push(instrumentId); @@ -378,7 +385,14 @@ $.each(sessionData.approved_rsvps, function(index, approved_rsvp) { if ("instrument_list" in approved_rsvp) { $.each(approved_rsvp.instrument_list, function(index, instrument) { - var instrumentId = context.JK.getInstrumentId(instrument.id); + var instrumentId; + + if (instrument) { + instrumentId = context.JK.getInstrumentId(instrument.id); + } + else { + instrumentId = 'other'; + } var inst = context.JK.getInstrumentIcon24(instrumentId); instrumentLogoHtml += ' '; }); diff --git a/web/app/assets/javascripts/dialog/sessionStartDialog.js b/web/app/assets/javascripts/dialog/sessionStartDialog.js new file mode 100644 index 000000000..971a5da65 --- /dev/null +++ b/web/app/assets/javascripts/dialog/sessionStartDialog.js @@ -0,0 +1,130 @@ +(function(context,$) { + + "use strict"; + context.JK = context.JK || {}; + context.JK.SessionStartDialog = function(app, session) { + var logger = context.JK.logger; + var sessionUtils = context.JK.SessionUtils; + var $dialog = null; + var dialogId = 'session-start-dialog'; + var $btnStartSession = null; + + function beforeShow(data) { + } + + function afterShow(data) { + } + + function afterHide() { + } + + function showDialog() { + return app.layout.showDialog(dialogId); + } + + function events() { + $btnStartSession.unbind('click'); + $btnStartSession.click(function(e) { + context.location = '/client#/session/' + session.id; + app.layout.closeDialog(dialogId); + }); + } + + function initializeSessionDetails() { + $dialog.find('#session-start-type-disp').html('Now!'); + $dialog.find('#session-name-disp').html(session.name); + $dialog.find('#session-description-disp').html(session.description); + + if (session.music_notations && session.music_notations.length > 0) { + $dialog.find('#session-notations-disp').html("Notations: " + session.music_notations.join(', ')); + } + + if (session.band) { + $dialog.find('#session-band-disp').html(band.name); + } + else { + $dialog.find('#session-band-disp').html('N/A'); + } + + $dialog.find('#session-language-disp').html(session.language_description); + + var invitedFriends = session.invitations; + + var sessionInvited = []; + $.each(invitedFriends, function(index, invitation) { + sessionInvited.push(invitation.receiver_name); + }); + + var sessionInvitedString = sessionInvited.join(', '); + + if (session.musician_access && session.approval_required) { + if (session.open_rsvps) { + if (invitedFriends.length == 0) + sessionInvitedString = "Any interested JamKazam musicians that I approve"; + else + sessionInvitedString += ", plus any interested JamKazam musicians that I approve"; + } + else { + if (invitedFriends.length == 0) { + sessionInvitedString = "No open RSVPs"; + } + else { + sessionInvitedString += " (No open RSVPs)"; + } + } + } + else if (session.musician_access && !session.approval_required) { + if (invitedFriends.length == 0) + sessionInvitedString = "Any interested JamKazam musicians who want to join us"; + else + sessionInvitedString += ", plus any interested JamKazam musicians who want to join us"; + } + + $dialog.find('#session-invited-disp').html(sessionInvitedString); + + var instrumentsMe = [], instrumentsOthers = []; + + $.each(session.approved_rsvps, function(index, rsvp) { + if (rsvp.id === context.JK.currentUserId) { + $.each(rsvp.instrument_list, function(index, instrument) { + instrumentsMe.push(instrument.desc); + }); + } + else { + $.each(rsvp.instrument_list, function(index, instrument) { + instrumentsOthers.push(instrument.desc); + }); + } + }); + + $dialog.find('#session-instruments-me-disp').html(instrumentsMe.join(', ')); + $dialog.find('#session-instruments-rsvp-disp').html(instrumentsOthers.join(', ')); + + $dialog.find('#session-musician-access-disp').html('Musicians: ' + session.musician_access_description); + $dialog.find('#session-fans-access-disp').html('Fans: ' + session.fan_access_description); + $dialog.find('#session-policy-disp').html(session.legal_policy); + } + + function initialize() { + + var dialogBindings = { + 'beforeShow' : beforeShow, + 'afterShow' : afterShow, + 'afterHide': afterHide + }; + + app.bindDialog(dialogId, dialogBindings); + + $dialog = $('[layout-id="' + dialogId + '"]'); + $btnStartSession = $dialog.find('.btnStartSession'); + + initializeSessionDetails(); + events(); + } + + this.initialize = initialize; + this.showDialog = showDialog; + } + + return this; +})(window,jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/dialog/textMessageDialog.js b/web/app/assets/javascripts/dialog/textMessageDialog.js index 78107a7f9..62b3b68f9 100644 --- a/web/app/assets/javascripts/dialog/textMessageDialog.js +++ b/web/app/assets/javascripts/dialog/textMessageDialog.js @@ -163,6 +163,7 @@ } $sendTextMessage.click(sendMessage); + // TODO: PULL FROM NEW TABLE rest.getNotifications(buildParams()) .done(function (response) { context._.each(response, function (textMessage) { diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js index 230e6d23b..37951e687 100644 --- a/web/app/assets/javascripts/jam_rest.js +++ b/web/app/assets/javascripts/jam_rest.js @@ -1258,6 +1258,7 @@ }); } + // TODO: push into new table function createTextMessage(options) { var id = getId(options); return $.ajax({ @@ -1269,6 +1270,17 @@ }); } + function getTextMessages(options) { + if(!options) options = {}; + var id = getId(options); + return $.ajax({ + type: "GET", + url: '/api/users/' + id + '/text_messages?' + $.param(options), + dataType: "json", + contentType: 'application/json' + }); + } + function getNotifications(options) { if(!options) options = {}; var id = getId(options); diff --git a/web/app/assets/javascripts/notificationPanel.js b/web/app/assets/javascripts/notificationPanel.js index 41fa918c4..ca393b18d 100644 --- a/web/app/assets/javascripts/notificationPanel.js +++ b/web/app/assets/javascripts/notificationPanel.js @@ -220,7 +220,7 @@ function updateNotificationList(response) { $.each(response, function(index, val) { - if(val.description == 'TEXT_MESSAGE') { + if(val.description == context.JK.MessageType.TEXT_MESSAGE) { val.formatted_msg = textMessageDialog.formatTextMessage(val.message.substring(0, 200), val.source_user_id, val.source_user.name, val.message.length > 200).html(); } diff --git a/web/app/assets/javascripts/scheduled_session.js.erb b/web/app/assets/javascripts/scheduled_session.js.erb index ebbd6a9b3..969f9449d 100644 --- a/web/app/assets/javascripts/scheduled_session.js.erb +++ b/web/app/assets/javascripts/scheduled_session.js.erb @@ -200,14 +200,14 @@ createSessionSettings.startDate = createSessionSettings.startDate || (new Date().toDateString()); - $("#session-start-date").val(createSessionSettings.startDate); + $screen.find("#session-start-date").val(createSessionSettings.startDate); toggleDate(true); toggleStartTime(); toggleStepStatus(); sessionUtils.defaultTimezone($timezoneList); if(firstTimeShown) { firstTimeShown = false; - $('#session-when-start-scheduled').iCheck('check'); + $screen.find('#session-when-start-scheduled').iCheck('check'); } } @@ -217,7 +217,7 @@ function beforeShowStep3() { rest.getBands(context.JK.currentUserId) .done(function(result) { - var options = $("#session-band-list"); + var options = $screen.find("#session-band-list"); options.empty(); options.append($("