VRFS-3190 : Merge with latest develop branch

This commit is contained in:
Steven Miers 2015-06-16 12:23:57 -05:00
commit cfda14e16e
52 changed files with 742 additions and 78 deletions

View File

@ -109,6 +109,7 @@ group :development, :test do
gem 'database_cleaner', '0.7.0' gem 'database_cleaner', '0.7.0'
gem 'launchy' gem 'launchy'
gem 'faker', '1.3.0' gem 'faker', '1.3.0'
gem 'puma'
end end
group :test do group :test do

View File

@ -0,0 +1,26 @@
ActiveAdmin.register JamRuby::BroadcastNotification, :as => 'BroadcastNotification' do
menu :label => 'Notifications'
config.sort_order = 'created_at_desc'
config.batch_actions = false
config.clear_action_items!
config.filters = false
action_item :only => :index do
link_to "New Broadcast" , "broadcast_notifications/new"
end
show do
attributes_table do
row :title
row :message
row :button_label
row :button_url
row :frequency
row :frequency_distribution
end
end
end

View File

@ -0,0 +1,20 @@
require 'csv'
class EmailController < ApplicationController
respond_to :csv
def dump_emailables
if params[:code] != Rails.application.config.email_dump_code
render :text => "", :status => 404
return
end
headers['Content-Disposition'] = "attachment; filename=\"user-list.csv\""
headers['Content-Type'] ||= 'text/csv'
@users = User.where(subscribe_email: true)
end
end

View File

@ -0,0 +1,2 @@
<%- headers = ['email', 'name', 'unsubscribe_token'] -%>
<%= CSV.generate_line headers %><%- @users.each do |user| -%><%= CSV.generate_line([user.email, user.name, user.unsubscribe_token]) %><%- end -%>

View File

@ -151,5 +151,7 @@ module JamAdmin
config.jamtracks_dir = ENV['JAMTRACKS_DIR'] || File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "jamtracks")) config.jamtracks_dir = ENV['JAMTRACKS_DIR'] || File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "jamtracks"))
config.jmep_dir = ENV['JMEP_DIR'] || File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "jmep")) config.jmep_dir = ENV['JMEP_DIR'] || File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "jmep"))
config.email_dump_code = 'rcAUyC3TZCbgGx4YQpznBRbNnQMXW5iKTzf9NSBfzMLsnw9dRQ'
end end
end end

View File

@ -34,6 +34,8 @@ JamAdmin::Application.routes.draw do
match '/api/mix/:id/enqueue' => 'admin/mixes#mix_again', :via => :post match '/api/mix/:id/enqueue' => 'admin/mixes#mix_again', :via => :post
match '/api/checks/latency_tester' => 'checks#check_latency_tester', :via => :get match '/api/checks/latency_tester' => 'checks#check_latency_tester', :via => :get
match '/api/users/emailables/:code' => 'email#dump_emailables', :via => :get
mount Resque::Server.new, :at => "/resque" mount Resque::Server.new, :at => "/resque"

View File

@ -16,3 +16,6 @@ PLATFORMS
DEPENDENCIES DEPENDENCIES
pg_migrate (= 0.1.13) pg_migrate (= 0.1.13)
BUNDLED WITH
1.10.3

View File

@ -292,4 +292,6 @@ signing.sql
optimized_redeemption.sql optimized_redeemption.sql
optimized_redemption_warn_mode.sql optimized_redemption_warn_mode.sql
affiliate_partners2.sql affiliate_partners2.sql
enhance_band_profile.sql enhance_band_profile.sql
broadcast_notifications.sql
broadcast_notifications_fk.sql

View File

@ -0,0 +1,22 @@
CREATE TABLE broadcast_notifications (
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
title VARCHAR(64),
message VARCHAR(256),
button_label VARCHAR(32),
button_url VARCHAR,
frequency INTEGER DEFAULT 0,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE broadcast_notification_views (
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id varchar(64) NOT NULL REFERENCES users(id),
broadcast_notification_id varchar(64) NOT NULL REFERENCES broadcast_notifications(id) ON DELETE CASCADE,
view_count INTEGER DEFAULT 0,
active_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX user_broadcast_idx ON broadcast_notification_views(user_id, broadcast_notification_id);

View File

@ -0,0 +1,2 @@
ALTER TABLE broadcast_notification_views DROP CONSTRAINT broadcast_notification_views_user_id_fkey;
ALTER TABLE broadcast_notification_views ADD CONSTRAINT broadcast_notification_views_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;

View File

@ -1,5 +1,5 @@
ALTER TABLE bands ADD COLUMN band_type VARCHAR(16) DEFAULT 'not specified'; ALTER TABLE bands ADD COLUMN band_type VARCHAR(16) DEFAULT '';
ALTER TABLE bands ADD COLUMN band_status VARCHAR(16) DEFAULT 'not specified'; ALTER TABLE bands ADD COLUMN band_status VARCHAR(16) DEFAULT '';
ALTER TABLE bands ADD COLUMN concert_count SMALLINT DEFAULT 0; ALTER TABLE bands ADD COLUMN concert_count SMALLINT DEFAULT 0;
ALTER TABLE bands ADD COLUMN add_new_members BOOLEAN DEFAULT FALSE; ALTER TABLE bands ADD COLUMN add_new_members BOOLEAN DEFAULT FALSE;
ALTER TABLE bands ADD COLUMN play_commitment SMALLINT DEFAULT 0; ALTER TABLE bands ADD COLUMN play_commitment SMALLINT DEFAULT 0;

View File

@ -24,7 +24,7 @@ gem 'uuidtools', '2.1.2'
gem 'bcrypt-ruby', '3.0.1' gem 'bcrypt-ruby', '3.0.1'
gem 'ruby-protocol-buffers', '1.2.2' gem 'ruby-protocol-buffers', '1.2.2'
gem 'eventmachine', '1.0.3' gem 'eventmachine', '1.0.3'
gem 'amqp', '1.0.2' gem 'amqp', '1.0.2'
gem 'will_paginate' gem 'will_paginate'
gem 'actionmailer', '3.2.13' gem 'actionmailer', '3.2.13'
gem 'sendgrid', '1.2.0' gem 'sendgrid', '1.2.0'
@ -63,6 +63,7 @@ group :test do
gem 'timecop' gem 'timecop'
gem 'rspec-prof' gem 'rspec-prof'
gem 'time_difference' gem 'time_difference'
gem 'byebug'
end end
# Specify your gem's dependencies in jam_ruby.gemspec # Specify your gem's dependencies in jam_ruby.gemspec

View File

@ -225,6 +225,8 @@ require "jam_ruby/models/text_message"
require "jam_ruby/models/sale" require "jam_ruby/models/sale"
require "jam_ruby/models/sale_line_item" require "jam_ruby/models/sale_line_item"
require "jam_ruby/models/recurly_transaction_web_hook" require "jam_ruby/models/recurly_transaction_web_hook"
require "jam_ruby/models/broadcast_notification"
require "jam_ruby/models/broadcast_notification_view"
require "jam_ruby/jam_tracks_manager" require "jam_ruby/jam_tracks_manager"
require "jam_ruby/jam_track_importer" require "jam_ruby/jam_track_importer"
require "jam_ruby/jmep_manager" require "jam_ruby/jmep_manager"

View File

@ -0,0 +1,51 @@
module JamRuby
class BroadcastNotification < ActiveRecord::Base
attr_accessible :title, :message, :button_label, :frequency, :button_url, as: :admin
has_many :user_views, class_name: 'JamRuby::BroadcastNotificationView', dependent: :destroy
validates :button_label, presence: true, length: {maximum: 14}
validates :message, presence: true, length: {maximum: 200}
validates :title, presence: true, length: {maximum: 50}
def self.next_broadcast(user)
self.viewable_notifications(user).limit(1).first
end
def self.viewable_notifications(user)
self.select('broadcast_notifications.*, bnv.updated_at AS bnv_updated_at')
.joins("LEFT OUTER JOIN broadcast_notification_views AS bnv ON bnv.broadcast_notification_id = broadcast_notifications.id AND (bnv.user_id IS NULL OR (bnv.user_id = '#{user.id}'))")
.where(['broadcast_notifications.frequency > 0'])
.where(['bnv.user_id IS NULL OR bnv.active_at < NOW()'])
.where(['bnv.user_id IS NULL OR broadcast_notifications.frequency > bnv.view_count'])
.order('bnv_updated_at NULLS FIRST')
end
def did_view(user)
bnv = BroadcastNotificationView
.where(broadcast_notification_id: self.id, user_id: user.id)
.limit(1)
.first
unless bnv
bnv = user_views.new()
bnv.user = user
bnv.active_at = Time.now - 10
end
bnv = user_views.new(user: user) unless bnv
bnv.view_count += 1
bnv.save
bnv
end
def frequency_distribution
@distribution ||= BroadcastNotificationView
.where(broadcast_notification_id: self.id)
.group(:view_count)
.count
end
end
end

View File

@ -0,0 +1,8 @@
module JamRuby
class BroadcastNotificationView < ActiveRecord::Base
belongs_to :broadcast_notification, :class_name => 'JamRuby::BroadcastNotification'
belongs_to :user, :class_name => 'JamRuby::User'
end
end

View File

@ -193,7 +193,7 @@ FactoryGirl.define do
end end
factory :invitation, :class => JamRuby::Invitation do factory :invitation, :class => JamRuby::Invitation do
end end
factory :friendship, :class => JamRuby::Friendship do factory :friendship, :class => JamRuby::Friendship do
@ -218,7 +218,7 @@ FactoryGirl.define do
band.genres << Genre.first band.genres << Genre.first
} }
end end
factory :genre, :class => JamRuby::Genre do factory :genre, :class => JamRuby::Genre do
description { |n| "Genre #{n}" } description { |n| "Genre #{n}" }
end end
@ -239,7 +239,7 @@ FactoryGirl.define do
end end
factory :video_source, :class => JamRuby::VideoSource do factory :video_source, :class => JamRuby::VideoSource do
#client_video_source_id "test_source_id" #client_video_source_id "test_source_id"
sequence(:client_video_source_id) { |n| "client_video_source_id#{n}"} sequence(:client_video_source_id) { |n| "client_video_source_id#{n}"}
end end
@ -269,7 +269,7 @@ FactoryGirl.define do
association :recording, factory: :recording association :recording, factory: :recording
end end
factory :recorded_video, :class => JamRuby::RecordedVideo do factory :recorded_video, :class => JamRuby::RecordedVideo do
sequence(:client_video_source_id) { |n| "client_video_source_id-#{n}"} sequence(:client_video_source_id) { |n| "client_video_source_id-#{n}"}
fully_uploaded true fully_uploaded true
length 1 length 1
@ -633,9 +633,9 @@ FactoryGirl.define do
data Faker::Lorem.sentence data Faker::Lorem.sentence
end end
factory :rsvp_slot, class: JamRuby::RsvpSlot do factory :rsvp_slot, :class => JamRuby::RsvpSlot do
proficiency_level 'beginner' proficiency_level "beginner"
instrument { Instrument.find('electric guitar') } instrument { Instrument.find('electric guitar') }
factory :chosen_rsvp_slot do factory :chosen_rsvp_slot do
@ -643,10 +643,10 @@ FactoryGirl.define do
user nil user nil
end end
after(:create) { |rsvp_slot, evaluator| after(:create) do |rsvp_slot, evaluator|
rsvp_request = FactoryGirl.create(:rsvp_request, user: evaluator.user) rsvp_request = FactoryGirl.create(:rsvp_request, user: evaluator.user)
rsvp_request_rsvp_slot = FactoryGirl.create(:rsvp_request_rsvp_slot, chosen:true, rsvp_request: rsvp_request, rsvp_slot:rsvp_slot) rsvp_request_rsvp_slot = FactoryGirl.create(:rsvp_request_rsvp_slot, chosen:true, rsvp_request: rsvp_request, rsvp_slot:rsvp_slot)
} end
end end
end end
@ -686,7 +686,7 @@ FactoryGirl.define do
end end
end end
factory :rsvp_request_rsvp_slot, class: JamRuby::RsvpRequestRsvpSlot do factory :rsvp_request_rsvp_slot, :class => JamRuby::RsvpRequestRsvpSlot do
chosen false chosen false
end end
@ -798,6 +798,13 @@ FactoryGirl.define do
end end
end end
factory :broadcast_notification, :class => JamRuby::BroadcastNotification do
title Faker::Lorem.sentence[0...50]
message Faker::Lorem.paragraph[0...200]
button_label Faker::Lorem.words(2).join(' ')[0...14]
frequency 3
end
factory :affiliate_partner, class: 'JamRuby::AffiliatePartner' do factory :affiliate_partner, class: 'JamRuby::AffiliatePartner' do
sequence(:partner_name) { |n| "partner-#{n}" } sequence(:partner_name) { |n| "partner-#{n}" }
entity_type 'Individual' entity_type 'Individual'
@ -824,6 +831,6 @@ FactoryGirl.define do
factory :affiliate_legalese, class: 'JamRuby::AffiliateLegalese' do factory :affiliate_legalese, class: 'JamRuby::AffiliateLegalese' do
legalese Faker::Lorem.paragraphs(6).join("\n\n") legalese Faker::Lorem.paragraphs(6).join("\n\n")
end end
end end

View File

@ -616,6 +616,7 @@ describe ActiveMusicSession do
end end
it "disallow a jam track to be opened when another is already opened" do it "disallow a jam track to be opened when another is already opened" do
pending "needs fixing"
# if a jam track is open, don't allow another to be opened # if a jam track is open, don't allow another to be opened
@music_session.open_jam_track(@user1, @jam_track) @music_session.open_jam_track(@user1, @jam_track)
@music_session.errors.any?.should be_false @music_session.errors.any?.should be_false

View File

@ -0,0 +1,64 @@
require 'spec_helper'
describe BroadcastNotification do
let(:broadcast1) { FactoryGirl.create(:broadcast_notification) }
let(:broadcast2) { FactoryGirl.create(:broadcast_notification) }
let(:broadcast3) { FactoryGirl.create(:broadcast_notification) }
let(:broadcast4) { FactoryGirl.create(:broadcast_notification) }
let(:user1) { FactoryGirl.create(:user) }
let(:user2) { FactoryGirl.create(:user) }
let(:user3) { FactoryGirl.create(:user) }
let(:user4) { FactoryGirl.create(:user) }
before(:each) do
BroadcastNotificationView.delete_all
end
it 'created broadcast' do
expect(broadcast1.title).not_to be_empty
expect(broadcast1.frequency).to be > 0
end
it 'gets view incremented' do
bnv = broadcast1.did_view(user1)
bnv = broadcast1.did_view(user1)
bnv.view_count.should eq(2)
end
it 'loads viewable broadcasts for a user' do
broadcast1.touch
broadcast2.touch
broadcast3.touch
broadcast4.touch
bns = BroadcastNotification.viewable_notifications(user1)
bns.count.should eq(4)
broadcast2.frequency.times { |nn| broadcast2.did_view(user1) }
broadcast3.did_view(user1)
broadcast1.did_view(user1)
broadcast4.did_view(user2)
bns = BroadcastNotification.viewable_notifications(user1)
expect(bns.count).to be(3)
expect(bns[0].id).to eq(broadcast3.id)
expect(bns.detect {|bb| bb.id==broadcast2.id }).to be_nil
expect(BroadcastNotification.next_broadcast(user1).id).to eq(broadcast3.id)
end
it 'generates frequency distribution' do
4.times { |nn| broadcast1.did_view(user1) }
5.times { |nn| broadcast1.did_view(user2) }
5.times { |nn| broadcast1.did_view(user3) }
8.times { |nn| broadcast1.did_view(user4) }
distrib = broadcast1.frequency_distribution
expect(distrib.count).to be == 3
expect(distrib[4]).to be == 1
expect(distrib[5]).to be == 2
expect(distrib[8]).to be == 1
end
end

View File

@ -1,7 +1,4 @@
source 'http://rubygems.org' source 'http://rubygems.org'
unless ENV["LOCAL_DEV"] == "1"
source 'https://jamjam:blueberryjam@int.jamkazam.com/gems/'
end
# Look for $WORKSPACE, otherwise use "workspace" as dev path. # Look for $WORKSPACE, otherwise use "workspace" as dev path.
devenv = ENV["BUILD_NUMBER"].nil? # Jenkins sets a build number environment variable devenv = ENV["BUILD_NUMBER"].nil? # Jenkins sets a build number environment variable
@ -12,11 +9,13 @@ if devenv
gem 'jam_ruby', :path => "../ruby" gem 'jam_ruby', :path => "../ruby"
gem 'jam_websockets', :path => "../websocket-gateway" gem 'jam_websockets', :path => "../websocket-gateway"
else else
gem 'jam_db', "0.1.#{ENV["BUILD_NUMBER"]}" source 'https://jamjam:blueberryjam@int.jamkazam.com/gems/' do
gem 'jampb', "0.1.#{ENV["BUILD_NUMBER"]}" gem 'jam_db', "0.1.#{ENV["BUILD_NUMBER"]}"
gem 'jam_ruby', "0.1.#{ENV["BUILD_NUMBER"]}" gem 'jampb', "0.1.#{ENV["BUILD_NUMBER"]}"
gem 'jam_websockets', "0.1.#{ENV["BUILD_NUMBER"]}" gem 'jam_ruby', "0.1.#{ENV["BUILD_NUMBER"]}"
ENV['NOKOGIRI_USE_SYSTEM_LIBRARIES'] ||= "true" gem 'jam_websockets', "0.1.#{ENV["BUILD_NUMBER"]}"
ENV['NOKOGIRI_USE_SYSTEM_LIBRARIES'] ||= "true"
end
end end
gem 'oj', '2.10.2' gem 'oj', '2.10.2'
@ -90,6 +89,13 @@ gem 'guard', '2.7.3'
gem 'influxdb', '0.1.8' gem 'influxdb', '0.1.8'
gem 'influxdb-rails', '0.1.10' gem 'influxdb-rails', '0.1.10'
gem 'sitemap_generator' gem 'sitemap_generator'
gem 'bower-rails', "~> 0.9.2"
gem 'react-rails', '~> 1.0'
#gem "browserify-rails", "~> 0.7"
source 'https://rails-assets.org' do
gem 'rails-assets-reflux'
end
group :development, :test do group :development, :test do
gem 'rspec-rails', '2.14.2' gem 'rspec-rails', '2.14.2'
@ -138,6 +144,7 @@ group :test, :cucumber do
# gem 'growl', '1.0.3' # gem 'growl', '1.0.3'
gem 'poltergeist' gem 'poltergeist'
gem 'resque_spec' gem 'resque_spec'
#gem 'thin'
end end

View File

@ -38,6 +38,7 @@
//= require jquery.exists //= require jquery.exists
//= require jquery.payment //= require jquery.payment
//= require jquery.visible //= require jquery.visible
//= require reflux
//= require howler.core.js //= require howler.core.js
//= require jstz //= require jstz
//= require class //= require class
@ -50,6 +51,10 @@
//= require utils //= require utils
//= require subscription_utils //= require subscription_utils
//= require custom_controls //= require custom_controls
//= require react
//= require react_ujs
//= require react-init
//= require react-components
//= require web/signup_helper //= require web/signup_helper
//= require web/signin_helper //= require web/signin_helper
//= require web/signin //= require web/signin
@ -59,4 +64,4 @@
//= require_directory ./wizard //= require_directory ./wizard
//= require_directory ./wizard/gear //= require_directory ./wizard/gear
//= require_directory ./wizard/loopback //= require_directory ./wizard/loopback
//= require everywhere/everywhere //= require everywhere/everywhere

View File

@ -1,18 +1,37 @@
# one time init stuff for the /client view # one time init stuff for the /client view
$ = jQuery $ = jQuery
context = window context = window
context.JK ||= {}; context.JK ||= {};
broadcastActions = context.JK.Actions.Broadcast
context.JK.ClientInit = class ClientInit context.JK.ClientInit = class ClientInit
constructor: () -> constructor: () ->
@logger = context.JK.logger @logger = context.JK.logger
@gearUtils = context.JK.GearUtils @gearUtils = context.JK.GearUtils
@ALERT_NAMES = context.JK.ALERT_NAMES;
@lastCheckedBroadcast = null
init: () => init: () =>
if context.gon.isNativeClient if context.gon.isNativeClient
this.nativeClientInit() this.nativeClientInit()
context.JK.onBackendEvent(@ALERT_NAMES.WINDOW_OPEN_FOREGROUND_MODE, 'client_init', @watchBroadcast);
this.watchBroadcast()
checkBroadcast: () =>
broadcastActions.load.triggerPromise()
watchBroadcast: () =>
if context.JK.currentUserId
# create a 15 second buffer to not check too fast for some reason (like the client firing multiple foreground events)
if !@lastCheckedBroadcast? || @lastCheckedBroadcast.getTime() < new Date().getTime() - 15000
@lastCheckedBroadcast = new Date()
setTimeout(@checkBroadcast, 3000)
nativeClientInit: () => nativeClientInit: () =>
@gearUtils.bootstrapDefaultPlaybackProfile(); @gearUtils.bootstrapDefaultPlaybackProfile();

View File

@ -4,6 +4,7 @@
context.JK = context.JK || {}; context.JK = context.JK || {};
context.JK.FakeJamClient = function(app, p2pMessageFactory) { context.JK.FakeJamClient = function(app, p2pMessageFactory) {
var ChannelGroupIds = context.JK.ChannelGroupIds;
var logger = context.JK.logger; var logger = context.JK.logger;
logger.info("*** Fake JamClient instance initialized. ***"); logger.info("*** Fake JamClient instance initialized. ***");
@ -474,7 +475,18 @@
} }
function SessionGetControlState(mixerIds, isMasterOrPersonal) { function SessionGetControlState(mixerIds, isMasterOrPersonal) {
dbg("SessionGetControlState"); dbg("SessionGetControlState");
var groups = [0, 1, 2, 3, 3, 7, 8, 10, 11, 12]; var groups = [
ChannelGroupIds.MasterGroup,
ChannelGroupIds.MonitorGroup,
ChannelGroupIds.AudioInputMusicGroup,
ChannelGroupIds.AudioInputChatGroup,
ChannelGroupIds.AudioInputChatGroup,
ChannelGroupIds.UserMusicInputGroup,
ChannelGroupIds.UserChatInputGroup,
ChannelGroupIds.PeerMediaTrackGroup,
ChannelGroupIds.JamTrackGroup,
ChannelGroupIds.MetronomeGroup
]
var names = [ var names = [
"FW AP Multi", "FW AP Multi",
"FW AP Multi", "FW AP Multi",

View File

@ -313,4 +313,26 @@
context.JK.NAMED_MESSAGES = { context.JK.NAMED_MESSAGES = {
MASTER_VS_PERSONAL_MIX : 'master_vs_personal_mix' MASTER_VS_PERSONAL_MIX : 'master_vs_personal_mix'
} }
// Recreate ChannelGroupIDs ENUM from C++
context.JK.ChannelGroupIds =
{
"MasterGroup": 0,
"MonitorGroup": 1,
"MasterCatGroup": 2,
"MonitorCatGroup": 3,
"AudioInputMusicGroup": 4,
"AudioInputChatGroup": 5,
"MediaTrackGroup": 6,
"StreamOutMusicGroup": 7,
"StreamOutChatGroup": 8,
"StreamOutMediaGroup": 9,
"UserMusicInputGroup": 10,
"UserChatInputGroup": 11,
"UserMediaInputGroup": 12,
"PeerAudioInputMusicGroup": 13,
"PeerMediaTrackGroup": 14,
"JamTrackGroup": 15,
"MetronomeGroup": 16
};
})(window,jQuery); })(window,jQuery);

View File

@ -56,7 +56,27 @@
}); });
} }
function uploadMusicNotations(formData) { function getBroadcastNotification(options) {
var userId = getId(options);
return $.ajax({
type: "GET",
url: "/api/users/" + userId + "/broadcast_notification"
});
}
function quietBroadcastNotification(options) {
var userId = getId(options);
var broadcast_id = options.broadcast_id;
return $.ajax({
type: "POST",
dataType: "json",
contentType: 'application/json',
url: "/api/users/" + userId + "/broadcast_notification/" + broadcast_id + '/quiet',
data: JSON.stringify({}),
});
}
function uploadMusicNotations(formData) {
return $.ajax({ return $.ajax({
type: "POST", type: "POST",
processData: false, processData: false,
@ -168,7 +188,7 @@
if (includePending) { if (includePending) {
includeFlag = 'true'; includeFlag = 'true';
} }
return $.ajax({ return $.ajax({
type: "GET", type: "GET",
dataType: "json", dataType: "json",
@ -1267,7 +1287,7 @@
function openBackingTrack(options) { function openBackingTrack(options) {
var musicSessionId = options["id"]; var musicSessionId = options["id"];
delete options["id"]; delete options["id"];
return $.ajax({ return $.ajax({
type: "POST", type: "POST",
dataType: "json", dataType: "json",
@ -1330,7 +1350,7 @@
function openMetronome(options) { function openMetronome(options) {
var musicSessionId = options["id"]; var musicSessionId = options["id"];
delete options["id"]; delete options["id"];
return $.ajax({ return $.ajax({
type: "POST", type: "POST",
dataType: "json", dataType: "json",
@ -1339,11 +1359,11 @@
data: JSON.stringify(options) data: JSON.stringify(options)
}) })
} }
function closeMetronome(options) { function closeMetronome(options) {
var musicSessionId = options["id"]; var musicSessionId = options["id"];
delete options["id"]; delete options["id"];
return $.ajax({ return $.ajax({
type: "POST", type: "POST",
dataType: "json", dataType: "json",
@ -1352,7 +1372,7 @@
data: JSON.stringify(options) data: JSON.stringify(options)
}) })
} }
function discardRecording(options) { function discardRecording(options) {
var recordingId = options["id"]; var recordingId = options["id"];
@ -1591,7 +1611,7 @@
url: '/api/recurly/payment_history', url: '/api/recurly/payment_history',
dataType: "json", dataType: "json",
contentType: 'application/json' contentType: 'application/json'
}); });
} }
function getSalesHistory(options) { function getSalesHistory(options) {
@ -1687,7 +1707,7 @@
function placeOrder() { function placeOrder() {
return $.ajax({ return $.ajax({
type: "POST", type: "POST",
url: '/api/recurly/place_order', url: '/api/recurly/place_order',
dataType: "json", dataType: "json",
contentType: 'application/json' contentType: 'application/json'
@ -1807,6 +1827,8 @@
this.createScheduledSession = createScheduledSession; this.createScheduledSession = createScheduledSession;
this.uploadMusicNotations = uploadMusicNotations; this.uploadMusicNotations = uploadMusicNotations;
this.getMusicNotation = getMusicNotation; this.getMusicNotation = getMusicNotation;
this.getBroadcastNotification = getBroadcastNotification;
this.quietBroadcastNotification = quietBroadcastNotification;
this.legacyJoinSession = legacyJoinSession; this.legacyJoinSession = legacyJoinSession;
this.joinSession = joinSession; this.joinSession = joinSession;
this.cancelSession = cancelSession; this.cancelSession = cancelSession;
@ -1895,7 +1917,7 @@
this.openJamTrack = openJamTrack this.openJamTrack = openJamTrack
this.openBackingTrack = openBackingTrack this.openBackingTrack = openBackingTrack
this.closeBackingTrack = closeBackingTrack this.closeBackingTrack = closeBackingTrack
this.closeMetronome = closeMetronome; this.closeMetronome = closeMetronome;
this.closeJamTrack = closeJamTrack; this.closeJamTrack = closeJamTrack;
this.openMetronome = openMetronome; this.openMetronome = openMetronome;
this.closeMetronome = closeMetronome; this.closeMetronome = closeMetronome;
@ -1960,9 +1982,6 @@
this.createAlert = createAlert; this.createAlert = createAlert;
this.signup = signup; this.signup = signup;
this.portOverCarts = portOverCarts; this.portOverCarts = portOverCarts;
return this; return this;
}; };
})(window,jQuery); })(window,jQuery);

View File

@ -202,7 +202,12 @@
var gridRows = layout.split('x')[0]; var gridRows = layout.split('x')[0];
var gridCols = layout.split('x')[1]; var gridCols = layout.split('x')[1];
$grid.children().each(function () { var gutterWidth = 0;
var findCardLayout;
var feedCardLayout;
$grid.find('.homecard').each(function () {
var childPosition = $(this).attr("layout-grid-position"); var childPosition = $(this).attr("layout-grid-position");
var childRow = childPosition.split(',')[1]; var childRow = childPosition.split(',')[1];
var childCol = childPosition.split(',')[0]; var childCol = childPosition.split(',')[0];
@ -211,6 +216,13 @@
var childLayout = me.getCardLayout(gridWidth, gridHeight, gridRows, gridCols, var childLayout = me.getCardLayout(gridWidth, gridHeight, gridRows, gridCols,
childRow, childCol, childRowspan, childColspan); childRow, childCol, childRowspan, childColspan);
if($(this).is('.feed')) {
feedCardLayout = childLayout;
}
else if($(this).is('.findsession')) {
findCardLayout = childLayout;
}
$(this).animate({ $(this).animate({
width: childLayout.width, width: childLayout.width,
height: childLayout.height, height: childLayout.height,
@ -218,6 +230,18 @@
left: childLayout.left left: childLayout.left
}, opts.animationDuration); }, opts.animationDuration);
}); });
var broadcastWidth = findCardLayout.width + feedCardLayout.width;
layoutBroadcast(broadcastWidth, findCardLayout.left);
}
function layoutBroadcast(width, left) {
var css = {
width:width + opts.gridPadding * 2,
left:left
}
$('[data-react-class="BroadcastHolder"]').animate(css, opts.animationDuration)
} }
function layoutSidebar(screenWidth, screenHeight) { function layoutSidebar(screenWidth, screenHeight) {

View File

@ -0,0 +1,3 @@
//= require ./react-components/actions/BroadcastActions
//= require ./react-components/stores/BroadcastStore
//= require_directory ./react-components

View File

@ -0,0 +1,45 @@
context = window
broadcastActions = window.JK.Actions.Broadcast;
rest = window.JK.Rest();
Broadcast = React.createClass({
displayName: 'Broadcast Notification'
handleNavigate: (e) ->
href = $(e.currentTarget).attr('href')
if href.indexOf('http') == 0
e.preventDefault()
window.JK.popExternalLink(href)
broadcastActions.hide.trigger()
notNow: (e) ->
e.preventDefault();
rest.quietBroadcastNotification({broadcast_id: this.props.notification.id})
broadcastActions.hide.trigger()
createMarkup: () ->
{__html: this.props.notification.message};
render: () ->
`<div className="broadcast-notification">
<div className="message" dangerouslySetInnerHTML={this.createMarkup()}/>
<div className="actions">
<div className="actionsAligner">
<a className="button-orange" onClick={this.handleNavigate}
href={this.props.notification.button_url}>{this.props.notification.button_label}</a>
<br/>
<a className="not-now" href="#" onClick={this.notNow}>not now, thanks</a>
</div>
</div>
</div>`
})
context.JK.Components.Broadcast = Broadcast
context.Broadcast = Broadcast

View File

@ -0,0 +1,27 @@
context = window
ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
BroadcastHolder = React.createClass(
{
displayName: 'Broadcast Notification Holder',
mixins: [Reflux.connect(context.JK.Stores.Broadcast, 'notification')]
render: ->
notification = []
if this.state.notification
notification.push(`<Broadcast key={this.state.notification.id} notification={this.state.notification}/>`)
`<div id="broadcast-notification-holder" className="broadcast-notification-holder" >
<ReactCSSTransitionGroup transitionName="bn-slidedown">
{notification}
</ReactCSSTransitionGroup>
</div>`
});
context.JK.Components.BroadcastHolder = BroadcastHolder
context.BroadcastHolder = BroadcastHolder

View File

@ -0,0 +1,8 @@
context = window
BroadcastActions = Reflux.createActions({
load: {asyncResult: true},
hide: {}
})
context.JK.Actions.Broadcast = BroadcastActions

View File

@ -0,0 +1,31 @@
$ = jQuery
context = window
logger = context.JK.logger
broadcastActions = context.JK.Actions.Broadcast
rest = context.JK.Rest()
broadcastActions.load.listenAndPromise(rest.getBroadcastNotification);
BroadcastStore = Reflux.createStore(
{
listenables: broadcastActions
onLoad: () ->
logger.debug("loading broadcast notification...")
onLoadCompleted: (response) ->
logger.debug("broadcast notification sync completed")
this.trigger(response)
onLoadFailed: (jqXHR) ->
if jqXHR.status != 404
logger.error("broadcast notification sync failed")
onHide: () ->
this.trigger(null)
}
)
context.JK.Stores.Broadcast = BroadcastStore

View File

@ -0,0 +1,3 @@
window.JK.Actions = {}
window.JK.Stores = {}
window.JK.Components = {}

View File

@ -14,6 +14,7 @@
var logger = context.JK.logger; var logger = context.JK.logger;
var self = this; var self = this;
var webcamViewer = new context.JK.WebcamViewer() var webcamViewer = new context.JK.WebcamViewer()
var ChannelGroupIds = context.JK.ChannelGroupIds;
var defaultParticipant = { var defaultParticipant = {
tracks: [{ tracks: [{
@ -39,23 +40,6 @@
height: 83 height: 83
}; };
// Recreate ChannelGroupIDs ENUM from C++
var ChannelGroupIds = {
"MasterGroup": 0,
"MonitorGroup": 1,
"AudioInputMusicGroup": 2,
"AudioInputChatGroup": 3,
"MediaTrackGroup": 4,
"StreamOutMusicGroup": 5,
"StreamOutChatGroup": 6,
"UserMusicInputGroup": 7,
"UserChatInputGroup": 8,
"PeerAudioInputMusicGroup": 9,
"PeerMediaTrackGroup": 10,
"JamTrackGroup": 11,
"MetronomeGroup": 12
};
var METRO_SOUND_LOOKUP = { var METRO_SOUND_LOOKUP = {
0 : "BuiltIn", 0 : "BuiltIn",
1 : "SineWave", 1 : "SineWave",

View File

@ -20,7 +20,7 @@
var userTracks = context.JK.TrackHelpers.getUserTracks(jamClient, allTracks); var userTracks = context.JK.TrackHelpers.getUserTracks(jamClient, allTracks);
var backingTracks = context.JK.TrackHelpers.getBackingTracks(jamClient, allTracks); var backingTracks = context.JK.TrackHelpers.getBackingTracks(jamClient, allTracks);
var metronomeTracks = context.JK.TrackHelpers.getTracks(jamClient, 12); var metronomeTracks = context.JK.TrackHelpers.getTracks(jamClient, 16);
return { return {
userTracks: userTracks, userTracks: userTracks,
@ -51,7 +51,7 @@
// allTracks is the result of SessionGetAllControlState; as an optimization // allTracks is the result of SessionGetAllControlState; as an optimization
getBackingTracks: function(jamClient, allTracks) { getBackingTracks: function(jamClient, allTracks) {
var mediaTracks = context.JK.TrackHelpers.getTracks(jamClient, 4, allTracks); var mediaTracks = context.JK.TrackHelpers.getTracks(jamClient, 6, allTracks);
var backingTracks = [] var backingTracks = []
context._.each(mediaTracks, function(mediaTrack) { context._.each(mediaTracks, function(mediaTrack) {
@ -80,7 +80,7 @@
var localMusicTracks = []; var localMusicTracks = [];
var i; var i;
localMusicTracks = context.JK.TrackHelpers.getTracks(jamClient, 2, allTracks); localMusicTracks = context.JK.TrackHelpers.getTracks(jamClient, 4, allTracks);
var trackObjects = []; var trackObjects = [];

View File

@ -84,4 +84,5 @@
*= require ./jamTrackPreview *= require ./jamTrackPreview
*= require users/signinCommon *= require users/signinCommon
*= require landings/partner_agreement_v1 *= require landings/partner_agreement_v1
*= require_directory ./react-components
*/ */

View File

@ -0,0 +1,79 @@
@import 'client/common';
[data-react-class="BroadcastHolder"] {
position:absolute;
min-height:60px;
top:62px;
@include border_box_sizing;
.broadcast-notification {
position:absolute;
border-width:1px;
border-color:$ColorScreenPrimary;
border-style:solid;
padding:5px 12px;
height:100%;
width:100%;
left:0;
top:0;
overflow:hidden;
margin-left:60px;
@include border_box_sizing;
}
.message {
float:left;
width:calc(100% - 150px);
font-size:12px;
}
.actions {
float:right;
text-align: right;
vertical-align: middle;
display:inline;
height:100%;
width:150px;
}
.actionsAligner {
display:inline-block;
vertical-align:middle;
text-align:center;
}
a { display:inline-block; }
.button-orange {
font-size:16px;
position:relative;
top:8px;
}
.not-now {
font-size:11px;
top:13px;
position:relative;
}
}
.bn-slidedown-enter {
max-height:0;
opacity: 0.01;
transition: max-height .5s ease-in;
}
.bn-slidedown-enter.bn-slidedown-enter-active {
max-height:60px;
opacity: 1;
}
.bn-slidedown-leave {
max-height:60px;
transition: max-height .5s ease-in;
}
.bn-slidedown-leave.bn-slidedown-leave-active {
max-height:0;
}

View File

@ -13,7 +13,7 @@ class ApiUsersController < ApiController
:band_invitation_index, :band_invitation_show, :band_invitation_update, # band invitations :band_invitation_index, :band_invitation_show, :band_invitation_update, # band invitations
:set_password, :begin_update_email, :update_avatar, :delete_avatar, :generate_filepicker_policy, :set_password, :begin_update_email, :update_avatar, :delete_avatar, :generate_filepicker_policy,
:share_session, :share_recording, :share_session, :share_recording,
:affiliate_report, :audio_latency] :affiliate_report, :audio_latency, :broadcast_notification]
respond_to :json respond_to :json
@ -856,6 +856,30 @@ class ApiUsersController < ApiController
end end
end end
def broadcast_notification
@broadcast = BroadcastNotification.next_broadcast(current_user)
if @broadcast
# mark it as viewed
@broadcast.did_view(current_user)
respond_with_model(@broadcast)
else
render json: { message: 'Not Found'}, status: 404
end
end
# used to hide a broadcast notification from rotation temporarily
def quiet_broadcast_notification
@broadcast = BroadcastNotificationView.find_by_broadcast_notification_id_and_user_id(params[:broadcast_id], current_user.id)
if @broadcast
@broadcast.active_at = Date.today + 14 # 14 days in the future we'll re-instas
@broadcast.save
end
render json: { }, status: 200
end
###################### RECORDINGS ####################### ###################### RECORDINGS #######################
# def recording_index # def recording_index
# @recordings = User.recording_index(current_user, params[:id]) # @recordings = User.recording_index(current_user, params[:id])

View File

@ -0,0 +1,3 @@
object @broadcast
attributes :id, :message, :button_label, :button_url

View File

@ -267,17 +267,17 @@ script type="text/template" id="template-help-profile-interests-virtual-band"
| For musicians who want to join a band that plays only online on JamKazam and does not travel and meet in person at a physical space to play. | For musicians who want to join a band that plays only online on JamKazam and does not travel and meet in person at a physical space to play.
script type="text/template" id="template-help-profile-interests-traditional-band" script type="text/template" id="template-help-profile-interests-traditional-band"
| For musicians who want to join a more traditional band that meets in person at a physical space to play. It may also play online on JamKazam. | For musicians who want to join a more traditional band that meets in person at a physical space to play. It may also play online on JamKazam.
script type="text/template" id="template-help-profile-interests-paid-sessions" script type="text/template" id="template-help-profile-interests-paid-sessions"
| For professional session musicians who are qualified and interested in playing paid recording session gigs for bands, composers, and producers. | For professional session musicians who are qualified and interested in playing paid recording session gigs for bands, composers, and producers.
script type="text/template" id="template-help-profile-interests-free-sessions" script type="text/template" id="template-help-profile-interests-free-sessions"
| For skilled musicians who are interested in playing free recording session gigs for bands, composers, and producers, either for fun or to build experience and resume. | For skilled musicians who are interested in playing free recording session gigs for bands, composers, and producers, either for fun or to build experience and resume.
script type="text/template" id="template-help-profile-interests-cowrite-partners" script type="text/template" id="template-help-profile-interests-cowrite-partners"
| For composers and songwriters who want to collaborate with others in composing and creating new music. | For composers and songwriters who want to collaborate with others in composing and creating new music.
script type="text/template" id="template-help-band-profile-add-new-member" script type="text/template" id="template-help-band-profile-add-new-member"
| For bands that want to add one or more new members and let the JamKazam community of musicians know your band is looking for new members. | For bands that want to add one or more new members and let the JamKazam community of musicians know your band is looking for new members.
@ -285,7 +285,7 @@ script type="text/template" id="template-help-band-profile-play-paid-gigs"
| For professional bands that are qualified and interested in playing paid gigs at music venues and other events. | For professional bands that are qualified and interested in playing paid gigs at music venues and other events.
script type="text/template" id="template-help-band-profile-play-free-gigs" script type="text/template" id="template-help-band-profile-play-free-gigs"
| For bands that are interested in playing free gigs, either for fun or to build experience. | For bands that are interested in playing free gigs, either for fun or to build experience.
script type="text/template" id="template-help-jamtrack-landing-preview" script type="text/template" id="template-help-jamtrack-landing-preview"
.jamtrack-landing-preview.big-help .jamtrack-landing-preview.big-help

View File

@ -10,7 +10,7 @@
/ Grid is the count of the smallest spaces, then / Grid is the count of the smallest spaces, then
/ individual spells span those spaces / individual spells span those spaces
-if @nativeClient -if @nativeClient
.grid layout-grid="2x12" .grid layout-grid="2x12"
.homecard.createsession layout-grid-columns="4" layout-grid-position="0,0" layout-grid-rows="1" layout-link="createSession" type="createSession" class="#{logged_in_not_logged_in_class}" .homecard.createsession layout-grid-columns="4" layout-grid-position="0,0" layout-grid-rows="1" layout-link="createSession" type="createSession" class="#{logged_in_not_logged_in_class}"
h2 create session h2 create session
.homebox-info .homebox-info
@ -45,7 +45,7 @@
.homebox-info .homebox-info
/! free service level /! free service level
-else -else
.grid layout-grid="2x12" .grid layout-grid="2x12"
.homecard.createsession layout-grid-columns="4" layout-grid-position="0,0" layout-grid-rows="1" layout-link="createSession" type="createSession" class="#{logged_in_not_logged_in_class}" .homecard.createsession layout-grid-columns="4" layout-grid-position="0,0" layout-grid-rows="1" layout-link="createSession" type="createSession" class="#{logged_in_not_logged_in_class}"
h2 create session h2 create session
.homebox-info .homebox-info

View File

@ -9,6 +9,7 @@
<div class="dialog-overlay op70" style="display:none; width:100%; height:100%; z-index:99;"></div> <div class="dialog-overlay op70" style="display:none; width:100%; height:100%; z-index:99;"></div>
<%= render "header" %> <%= render "header" %>
<%= react_component 'BroadcastHolder', {} %>
<%= render "home" %> <%= render "home" %>
<%= render "footer" %> <%= render "footer" %>
<%= render "paginator" %> <%= render "paginator" %>
@ -84,6 +85,7 @@
<%= render 'dialogs/dialogs' %> <%= render 'dialogs/dialogs' %>
<div id="fb-root"></div> <div id="fb-root"></div>
<script type="text/javascript"> <script type="text/javascript">
$(function() { $(function() {

8
web/bower.json Normal file
View File

@ -0,0 +1,8 @@
{
"vendor": {
"name": "bower-rails generated vendor assets",
"dependencies": {
"fluxxor": "1.5.4"
}
}
}

View File

@ -34,8 +34,10 @@ bundle install --path vendor/bundle
#bundle update #bundle update
set +e set +e
echo "cleaning assets from last build"
# clean assets, because they may be lingering from last build # clean assets, because they may be lingering from last build
bundle exec rake assets:clean # bundle exec rake assets:clean
rm -rf $DIR/public/assets
if [ "$?" = "0" ]; then if [ "$?" = "0" ]; then
echo "success: updated dependencies" echo "success: updated dependencies"

View File

@ -77,6 +77,7 @@ if defined?(Bundler)
# Add the assets/fonts directory to assets.paths # Add the assets/fonts directory to assets.paths
config.assets.paths << "#{Rails.root}/app/assets/fonts" config.assets.paths << "#{Rails.root}/app/assets/fonts"
config.assets.paths << Rails.root.join('vendor', 'assets', 'bower_components')
# Precompile additional assets (application.js, application.css, and all non-JS/CSS (i.e., images) are already added) # Precompile additional assets (application.js, application.css, and all non-JS/CSS (i.e., images) are already added)
config.assets.precompile += %w( client/client.css ) config.assets.precompile += %w( client/client.css )
@ -85,7 +86,6 @@ if defined?(Bundler)
config.assets.precompile += %w( web/web.js web/web.css ) config.assets.precompile += %w( web/web.js web/web.css )
config.assets.precompile += %w( minimal/minimal.js minimal/minimal.css ) config.assets.precompile += %w( minimal/minimal.js minimal/minimal.css )
# where is rabbitmq? # where is rabbitmq?
config.rabbitmq_host = "localhost" config.rabbitmq_host = "localhost"
config.rabbitmq_port = 5672 config.rabbitmq_port = 5672
@ -348,5 +348,8 @@ if defined?(Bundler)
config.web_performance_timing_enabled = true config.web_performance_timing_enabled = true
config.jamtrack_landing_bubbles_enabled = true config.jamtrack_landing_bubbles_enabled = true
config.jamtrack_browser_bubbles_enabled = true config.jamtrack_browser_bubbles_enabled = true
config.react.variant = :production
config.react.addons = true
end end
end end

View File

@ -96,4 +96,6 @@ SampleApp::Application.configure do
config.email_generic_from = 'nobody-dev@jamkazam.com' config.email_generic_from = 'nobody-dev@jamkazam.com'
config.email_alerts_alias = ENV['ALERT_EMAIL'] || 'alerts-dev@jamkazam.com' config.email_alerts_alias = ENV['ALERT_EMAIL'] || 'alerts-dev@jamkazam.com'
config.guard_against_fraud = true config.guard_against_fraud = true
config.react.variant = :development
end end

View File

@ -141,7 +141,7 @@ SampleApp::Application.routes.draw do
match '/about', to: 'corps#about', as: 'corp_about' match '/about', to: 'corps#about', as: 'corp_about'
# news routes # news routes
match '/news', to: 'corps#news', as: 'corp_news' match '/news', to: 'corps#news', as: 'corp_news'
# media center routes # media center routes
match '/media_center', to: 'corps#media_center', as: 'corp_media_center' match '/media_center', to: 'corps#media_center', as: 'corp_media_center'
@ -237,7 +237,7 @@ SampleApp::Application.routes.draw do
match '/shopping_carts' => 'api_shopping_carts#index', :via => :get match '/shopping_carts' => 'api_shopping_carts#index', :via => :get
match '/shopping_carts' => 'api_shopping_carts#remove_cart', :via => :delete match '/shopping_carts' => 'api_shopping_carts#remove_cart', :via => :delete
match '/shopping_carts/clear_all' => 'api_shopping_carts#clear_all', :via => :delete match '/shopping_carts/clear_all' => 'api_shopping_carts#clear_all', :via => :delete
# RSVP requests # RSVP requests
match '/rsvp_requests' => 'api_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' => 'api_rsvp_requests#create', :via => :post
@ -268,7 +268,7 @@ SampleApp::Application.routes.draw do
match '/users' => 'api_users#index', :via => :get match '/users' => 'api_users#index', :via => :get
match '/users' => 'api_users#create', :via => :post match '/users' => 'api_users#create', :via => :post
match '/users/:id' => 'api_users#show', :via => :get, :as => 'api_user_detail' match '/users/:id' => 'api_users#show', :via => :get, :as => 'api_user_detail'
#match '/users' => 'api_users#create', :via => :post #match '/users' => 'api_users#create', :via => :post
match '/users/:id' => 'api_users#update', :via => :post match '/users/:id' => 'api_users#update', :via => :post
match '/users/:id' => 'api_users#delete', :via => :delete match '/users/:id' => 'api_users#delete', :via => :delete
match '/users/confirm/:signup_token' => 'api_users#signup_confirm', :via => :post, :as => 'api_signup_confirmation' match '/users/confirm/:signup_token' => 'api_users#signup_confirm', :via => :post, :as => 'api_signup_confirmation'
@ -277,7 +277,7 @@ SampleApp::Application.routes.draw do
match '/users/:id/set_password' => 'api_users#set_password', :via => :post match '/users/:id/set_password' => 'api_users#set_password', :via => :post
# recurly # recurly
match '/recurly/create_account' => 'api_recurly#create_account', :via => :post match '/recurly/create_account' => 'api_recurly#create_account', :via => :post
match '/recurly/delete_account' => 'api_recurly#delete_account', :via => :delete match '/recurly/delete_account' => 'api_recurly#delete_account', :via => :delete
match '/recurly/get_account' => 'api_recurly#get_account', :via => :get match '/recurly/get_account' => 'api_recurly#get_account', :via => :get
match '/recurly/payment_history' => 'api_recurly#payment_history', :via => :get match '/recurly/payment_history' => 'api_recurly#payment_history', :via => :get
@ -311,7 +311,7 @@ SampleApp::Application.routes.draw do
match '/users/:id/likings' => 'api_users#liking_index', :via => :get, :as => 'api_user_liking_index' match '/users/:id/likings' => 'api_users#liking_index', :via => :get, :as => 'api_user_liking_index'
match '/users/:id/likings' => 'api_users#liking_create', :via => :post match '/users/:id/likings' => 'api_users#liking_create', :via => :post
match '/users/:id/likings/:likable_id' => 'api_users#liking_destroy', :via => :delete match '/users/:id/likings/:likable_id' => 'api_users#liking_destroy', :via => :delete
# user followers # user followers
match '/users/:id/followers' => 'api_users#follower_index', :via => :get, :as => 'api_user_follower_index' match '/users/:id/followers' => 'api_users#follower_index', :via => :get, :as => 'api_user_follower_index'
@ -379,6 +379,10 @@ SampleApp::Application.routes.draw do
match '/users/:id/profile' => 'api_users#profile_show', :via => :get, :as => 'api_users_profile_show' match '/users/:id/profile' => 'api_users#profile_show', :via => :get, :as => 'api_users_profile_show'
match '/users/:id/profile' => 'api_users#profile_save', :via => :post match '/users/:id/profile' => 'api_users#profile_save', :via => :post
# broadcast notification
match '/users/:id/broadcast_notification' => 'api_users#broadcast_notification', :via => :get
match '/users/:id/broadcast_notification/:broadcast_id/quiet' => 'api_users#quiet_broadcast_notification', :via => :post
# session chat # session chat
match '/chat' => 'api_chats#create', :via => :post match '/chat' => 'api_chats#create', :via => :post
match '/sessions/:music_session/chats' => 'api_chats#index', :via => :get match '/sessions/:music_session/chats' => 'api_chats#index', :via => :get
@ -426,7 +430,7 @@ SampleApp::Application.routes.draw do
# band likers # band likers
match '/bands/:id/likers' => 'api_bands#liker_index', :via => :get match '/bands/:id/likers' => 'api_bands#liker_index', :via => :get
# band followers # band followers
match '/bands/:id/followers' => 'api_bands#follower_index', :via => :get match '/bands/:id/followers' => 'api_bands#follower_index', :via => :get
@ -469,13 +473,13 @@ SampleApp::Application.routes.draw do
match '/join_requests' => 'api_join_requests#create', :via => :post match '/join_requests' => 'api_join_requests#create', :via => :post
match '/join_requests/:id' => 'api_join_requests#update', :via => :put match '/join_requests/:id' => 'api_join_requests#update', :via => :put
match '/join_requests' => 'api_join_requests#index', :via => :get match '/join_requests' => 'api_join_requests#index', :via => :get
# Location lookups # Location lookups
match '/countries' => 'api_maxmind_requests#countries', :via => :get match '/countries' => 'api_maxmind_requests#countries', :via => :get
match '/regions' => 'api_maxmind_requests#regions', :via => :get match '/regions' => 'api_maxmind_requests#regions', :via => :get
match '/cities' => 'api_maxmind_requests#cities', :via => :get match '/cities' => 'api_maxmind_requests#cities', :via => :get
match '/resolved_location' => 'api_maxmind_requests#resolved_location', :via => :get match '/resolved_location' => 'api_maxmind_requests#resolved_location', :via => :get
# Recordings # Recordings
match '/recordings/uploads' => 'api_recordings#list_uploads', :via => :get, :as => 'api_recordings_list_uploads' match '/recordings/uploads' => 'api_recordings#list_uploads', :via => :get, :as => 'api_recordings_list_uploads'

16
web/package.json Normal file
View File

@ -0,0 +1,16 @@
{
"name": "jam-web",
"dependencies" : {
"browserify": "~> 6.3",
"browserify-incremental": "^1.4.0",
"coffeeify": "~> 0.6",
"reactify": "^0.17.1",
"coffee-reactify": "~>3.0.0",
"react": "^0.12.0",
"react-tools": "^0.12.1"
},
"engines": {
"node": ">= 0.10"
}
}

View File

@ -778,6 +778,13 @@ FactoryGirl.define do
end end
end end
factory :broadcast_notification, :class => JamRuby::BroadcastNotification do
title Faker::Lorem.sentence[0...50]
message Faker::Lorem.paragraph[0...200]
button_label Faker::Lorem.words(2).join(' ')[0...14]
frequency 3
end
factory :affiliate_partner, class: 'JamRuby::AffiliatePartner' do factory :affiliate_partner, class: 'JamRuby::AffiliatePartner' do
sequence(:partner_name) { |n| "partner-#{n}" } sequence(:partner_name) { |n| "partner-#{n}" }
entity_type 'Individual' entity_type 'Individual'

View File

@ -0,0 +1,36 @@
require 'spec_helper'
describe "affiliate visit tracking" do
subject { page }
let(:user) { FactoryGirl.create(:user) }
let(:partner) { FactoryGirl.create(:affiliate_partner) }
let(:affiliate_params) { partner.affiliate_query_params }
before(:each) do
AffiliateReferralVisit.delete_all
end
it "tracks" do
visit '/?' + affiliate_params
should_be_at_root
AffiliateReferralVisit.count.should eq(1)
visit = AffiliateReferralVisit.first
visit.visited_url.should eq('/?' + affiliate_params)
visit.affiliate_partner_id.should eq(partner.id)
visit.first_visit.should be_true
download_url = '/downloads?' + affiliate_params
visit download_url
find('h2.create-account-header')
AffiliateReferralVisit.count.should eq(2)
visit = AffiliateReferralVisit.find_by_visited_url(download_url)
visit.affiliate_partner_id.should eq(partner.id)
visit.first_visit.should be_false
end
end

View File

@ -0,0 +1,40 @@
require 'spec_helper'
describe "broadcast notification", :js => true, :type => :feature, :capybara_feature => true do
subject { page }
let(:broadcast1) {FactoryGirl.create(:broadcast_notification, frequency: 4)}
let(:broadcast2) {FactoryGirl.create(:broadcast_notification, frequency: 4)}
let(:user) { FactoryGirl.create(:user) }
let(:partner) { FactoryGirl.create(:affiliate_partner) }
before(:each) do
BroadcastNotificationView.delete_all
BroadcastNotification.delete_all
end
it "cycles on home screen" do
broadcast1.touch
fast_signin(user, '/client')
find('.broadcast-notification .message', text: broadcast1.message)
broadcast2.touch
visit current_path
find('.broadcast-notification .message', text: broadcast2.message)
visit current_path
find('.broadcast-notification .message', text: broadcast1.message)
find('.broadcast-notification .not-now').trigger(:click)
visit current_path
find('.broadcast-notification .message', text: broadcast2.message)
go_to_root
visit '/client'
find('.broadcast-notification .message', text: broadcast2.message)
end
end

File diff suppressed because one or more lines are too long