Merge branch 'develop' of bitbucket.org:jamkazam/jam-cloud into develop

This commit is contained in:
Seth Call 2014-02-01 04:51:49 +00:00
commit 4e89a686e2
98 changed files with 1447 additions and 580 deletions

View File

@ -11,6 +11,7 @@ else
gem 'jam_db', "0.1.#{ENV["BUILD_NUMBER"]}"
gem 'jampb', "0.1.#{ENV["BUILD_NUMBER"]}"
gem 'jam_ruby', "0.1.#{ENV["BUILD_NUMBER"]}"
ENV['NOKOGIRI_USE_SYSTEM_LIBRARIES'] ||= "true"
end
gem 'rails'

View File

@ -222,7 +222,7 @@ ActiveAdmin.register_page "Bootstrap" do
elsif IcecastMountTemplate.count == 0
semantic_form_for IcecastMountTemplate.new, :url => admin_bootstrap_create_mount_template_path, :builder => ActiveAdmin::FormBuilder do |f|
f.inputs "New Mount Template" do
f.input :hostname, :label => "jam-web hostname:port"
f.input :hostname, :label => "jam-web public hostname:port (such that icecast can reach it)"
f.input :default_mime_type, :as => :select, :collection => ["ogg", "mp3"]
end
f.actions

View File

@ -70,6 +70,13 @@ ActiveAdmin.register JamRuby::User, :as => 'User Progression' do
''
end
end
column 'Recorded' do |uu|
if dd = uu.first_recording_at
dd.strftime(PROGRESSION_DATE)
else
''
end
end
column 'Promoted' do |uu|
if dd = uu.first_social_promoted_at
dd.strftime(PROGRESSION_DATE)
@ -77,10 +84,6 @@ ActiveAdmin.register JamRuby::User, :as => 'User Progression' do
''
end
end
column 'Recorded' do |uu|
uu.first_recording_at
end
end
end

View File

@ -4,7 +4,6 @@ class ApplicationController < ActionController::Base
before_filter :prepare_gon
def prepare_gon
gon.another = 'hello'
gon.prefix = ENV['RAILS_RELATIVE_URL_ROOT'] || '/'
end
end

View File

@ -32,7 +32,7 @@ listen 3100, :tcp_nopush => true
timeout 30
# feel free to point this anywhere accessible on the filesystem
pid "/var/run/jam-admin.pid"
pid "/var/run/jam-admin/jam-admin.pid"
# By default, the Unicorn logger will write to stderr.
# Additionally, ome applications/frameworks log to stderr or stdout,

View File

@ -7,24 +7,6 @@ echo "starting build..."
if [ "$?" = "0" ]; then
echo "build succeeded"
if [ ! -z "$PACKAGE" ]; then
if [[ "$GIT_BRANCH" == *develop* || "$GIT_BRANCH" == *master* ]]; then
echo "publishing ubuntu package (.deb)"
DEBPATH=`find target/deb -name *.deb`
DEBNAME=`basename $DEBPATH`
curl -f -T $DEBPATH $DEB_SERVER/$DEBNAME
if [ "$?" != "0" ]; then
echo "deb publish failed"
exit 1
fi
echo "done publishing deb"
else
echo "Skipping publish since branch is neither master or develop..."
fi
fi
else
echo "build failed"
exit 1

View File

@ -3,5 +3,7 @@ description "jam-admin"
start on startup
start on runlevel [2345]
stop on runlevel [016]
setuid jam-admin
setgid jam-admin
exec start-stop-daemon --start --chdir /var/lib/jam-admin --exec /var/lib/jam-admin/script/package/upstart-run.sh

View File

@ -14,7 +14,9 @@ mkdir -p /var/lib/$NAME/log
mkdir -p /var/lib/$NAME/tmp
mkdir -p /etc/$NAME
mkdir -p /var/log/$NAME
mkdir -p /var/run/$NAME
chown -R $USER:$GROUP /var/lib/$NAME
chown -R $USER:$GROUP /etc/$NAME
chown -R $USER:$GROUP /var/log/$NAME
chown -R $USER:$GROUP /var/run/$NAME

133
build Executable file
View File

@ -0,0 +1,133 @@
#!/bin/bash
# RUN_SLOW_TESTS, RUN_AWS_TESTS, SKIP_KARMA=1 SHOW_JS_ERRORS=1 PACKAGE=1
# WORKSPACE=/var/lib/jenkins/jobs/jam-web/workspace
export BUNDLE_JOBS=1 # 6, which i want to use, makes the whole server crawl
echo ""
echo "BUILDING JAM-DB"
pushd db > /dev/null
./jenkins
popd > /dev/null
echo ""
echo "BUILDING JAM-PB"
pushd pb > /dev/null
bash -l ./jenkins
popd > /dev/null
echo ""
echo "BUILDING JAM-RUBY"
pushd ruby > /dev/null
rm -f *.gem
bash -l ./jenkins
popd > /dev/null
echo ""
echo "BUILDING WEBSOCKET GATEWAY"
pushd websocket-gateway > /dev/null
bash -l ./jenkins
popd > /dev/null
echo ""
echo "BUILDING JAM-WEB"
pushd web > /dev/null
echo "kill any stuck rspec tests from previous run. need to debug how/why this happens on build server"
set +e
ps aux | grep -ie "jam-cloud.*rspec" | awk '{print $2}' | xargs kill -9
set -e
PACKAGE=1 bash ./jenkins
# we do this so that the build won't fail in jenkins if no capybara error screenshot isn't there
mkdir -p tmp/capybara
touch tmp/capybara/success.png
popd > /dev/null
echo ""
echo "BUILDING JAM-ADMIN"
pushd admin /dev/null
bash -l ./jenkins
popd > /dev/null
if [ ! -z "$PACKAGE" ]; then
DEB_SERVER=http://localhost:9010/apt-`uname -p`
GEM_SERVER=http://localhost:9000/gems
# if still going, then push all debs up
if [[ "$GIT_BRANCH" == *develop* || "$GIT_BRANCH" == *master* ]]; then
echo ""
echo "PUSHING DB ARTIFACTS"
pushd db > /dev/null
echo "publishing ubuntu packages (.deb)"
for f in `find target -name '*.deb'`; do
DEBNAME=`basename $f`
DEBPATH="$f"
echo "publishing $DEBPATH to deb server"
curl -f -T $DEBPATH $DEB_SERVER/$DEBNAME
if [ "$?" != "0" ]; then
echo "deb publish failed of $DEBPATH"
exit 1
fi
done
echo "done publishing debs"
popd > /dev/null
echo ""
echo "PUSHING WEB"
pushd web > /dev/null
echo "publishing ubuntu package (.deb)"
DEBPATH=`find target/deb -name *.deb`
DEBNAME=`basename $DEBPATH`
curl -f -T $DEBPATH $DEB_SERVER/$DEBNAME
if [ "$?" != "0" ]; then
echo "deb publish failed"
exit 1
fi
echo "done publishing deb"
popd > /dev/null
echo ""
echo "PUSHING WEBSOCKET-GATEWAY"
pushd websocket-gateway > /dev/null
echo "publishing ubuntu package (.deb)"
DEBPATH=`find target/deb -name *.deb`
DEBNAME=`basename $DEBPATH`
curl -f -T $DEBPATH $DEB_SERVER/$DEBNAME
if [ "$?" != "0" ]; then
echo "deb publish failed"
exit 1
fi
echo "done publishing deb"
popd > /dev/null
echo ""
echo "PUSHING ADMIN"
pushd admin > /dev/null
echo "publishing ubuntu package (.deb)"
DEBPATH=`find target/deb -name *.deb`
DEBNAME=`basename $DEBPATH`
curl -f -T $DEBPATH $DEB_SERVER/$DEBNAME
if [ "$?" != "0" ]; then
echo "deb publish failed"
exit 1
fi
echo "done publishing deb"
popd > /dev/null
else
echo "Skipping publish since branch is neither master or develop..."
fi
fi

View File

@ -8,37 +8,17 @@ echo "starting build..."
if [ "$?" = "0" ]; then
echo "build succeeded"
if [[ "$GIT_BRANCH" == *develop* || "$GIT_BRANCH" == *master* ]]; then
echo "publishing gem"
pushd "target/ruby_package"
find . -name *.gem -exec curl -f -T {} $GEM_SERVER/{} \;
echo "publishing gem"
pushd "target/ruby_package"
find . -name *.gem -exec curl -f -T {} $GEM_SERVER/{} \;
if [ "$?" != "0" ]; then
echo "publish failed"
exit 1
fi
if [ "$?" != "0" ]; then
echo "publish failed"
exit 1
fi
popd
echo "done publishing gems"
if [ ! -z "$PACKAGE" ]; then
echo "publishing ubuntu packages (.deb)"
for f in `find target -name '*.deb'`; do
DEBNAME=`basename $f`
DEBPATH="$f"
echo "publishing $DEBPATH to deb server"
curl -f -T $DEBPATH $DEB_SERVER/$DEBNAME
if [ "$?" != "0" ]; then
echo "deb publish failed of $DEBPATH"
exit 1
fi
done
echo "done publishing debs"
fi
else
echo "Skipping publish since branch is neither master or develop..."
fi
popd
echo "done publishing gems"
else
echo "build failed"
exit 1

View File

@ -93,4 +93,7 @@ music_sessions_unlogged.sql
integrate_icecast_into_sessions.sql
ms_recording_anonymous_likes.sql
ms_user_history_add_instruments.sql
icecast_config_changed.sql
icecast_config_changed.sql
invited_users_facebook_support.sql
first_recording_at.sql
share_token.sql

View File

@ -0,0 +1 @@
alter table users add column first_recording_at TIMESTAMP;

View File

@ -0,0 +1,3 @@
ALTER TABLE invited_users ALTER COLUMN email DROP NOT NULL;
ALTER TABLE invited_users ADD COLUMN invite_medium VARCHAR(64);

2
db/up/share_token.sql Normal file
View File

@ -0,0 +1,2 @@
alter table music_sessions_history add column share_token varchar(15);
alter table claimed_recordings add column share_token varchar(15);

View File

@ -10,7 +10,7 @@ if [ "$?" = "0" ]; then
echo "publishing gem"
pushd "target/ruby/jampb"
find . -name *.gem -exec curl -f -T {} $GEM_SERVER/{} \;
if [ "$?" != "0" ]; then
echo "publish failed"
exit 1

View File

@ -6,6 +6,15 @@ end
devenv = ENV["BUILD_NUMBER"].nil? # Jenkins sets a build number environment variable
if devenv
gem 'jam_db', :path=> "../db/target/ruby_package"
gem 'jampb', :path => "../pb/target/ruby/jampb"
else
gem 'jam_db'
gem 'jampb'
ENV['NOKOGIRI_USE_SYSTEM_LIBRARIES'] ||= "true"
end
gem 'pg', '0.15.1', :platform => [:mri, :mswin, :mingw]
gem 'jdbc_postgres', :platform => [:jruby]
@ -33,14 +42,6 @@ gem 'resque-lonely_job', '~> 1.0.0'
gem 'oj'
gem 'builder'
if devenv
gem 'jam_db', :path=> "../db/target/ruby_package"
gem 'jampb', :path => "../pb/target/ruby/jampb"
else
gem 'jam_db'
gem 'jampb'
end
group :test do
gem "factory_girl", '4.1.0'
gem "rspec", "2.11"

View File

@ -26,7 +26,7 @@ EOF
echo "publishing gem"
curl -f -T $GEMNAME $GEM_SERVER/$GEMNAME
if [ "$?" != "0" ]; then
echo "publish failed"
exit 1

View File

@ -31,6 +31,7 @@ require "jam_ruby/lib/profanity"
require "jam_ruby/lib/em_helper.rb"
require "jam_ruby/resque/audiomixer"
require "jam_ruby/resque/icecast_config_writer"
require "jam_ruby/resque/resque_hooks"
require "jam_ruby/resque/scheduled/audiomixer_retry"
require "jam_ruby/resque/scheduled/icecast_config_retry"
require "jam_ruby/resque/scheduled/icecast_source_check"

View File

@ -48,7 +48,8 @@ module JamRuby
end
def generate_signup_url(invited_user)
"http://www.jamkazam.com/signup?invitation_code=#{invited_user.invitation_code}"
invited_user.generate_signup_url
# "http://www.jamkazam.com/signup?invitation_code=#{invited_user.invitation_code}"
end
end
end

View File

@ -63,8 +63,15 @@ module JamRuby
end
def recent_history
recordings = ClaimedRecording.joins(:recordings).where(:recordings => {:band_id => "#{self.id}"}).limit(10)
msh = MusicSessionHistory.where(:band_id => self.id).limit(10)
recordings = ClaimedRecording.joins(:recordings)
.where(:recordings => {:band_id => "#{self.id}"})
.order('created_at DESC')
.limit(10)
msh = MusicSessionHistory.where(:band_id => self.id)
.order('created_at DESC')
.limit(10)
recordings.concat(msh)
recordings.sort! {|a,b| b.created_at <=> a.created_at}.first(5)
end

View File

@ -15,6 +15,10 @@ module JamRuby
has_many :recorded_tracks, :through => :recording, :class_name => "JamRuby::RecordedTrack"
has_many :playing_sessions, :class_name => "JamRuby::MusicSession"
before_create :generate_share_token
SHARE_TOKEN_LENGTH = 8
# user must own this object
# params is a hash, and everything is optional
def update_fields(user, params)
@ -42,5 +46,20 @@ module JamRuby
self.destroy
end
end
def remove_non_alpha_num(token)
token.gsub(/[^0-9A-Za-z]/, '')
end
private
def generate_share_token
self.share_token = loop do
token = SecureRandom.urlsafe_base64(SHARE_TOKEN_LENGTH, false)
token = remove_non_alpha_num(token)
token.upcase!
break token unless MusicSessionHistory.exists?(share_token: token)
end
end
end
end

View File

@ -3,8 +3,9 @@ module JamRuby
attr_accessor :skip_config_changed_flag
attr_accessible :template_id, :mount_template_id, :limit_id, :admin_auth_id, :directory_id, :master_relay_id, :path_id, :logging_id,
:security_id, :config_changed, :config_updated_at, :hostname, :location, :admin_email, :fileserve, :icecast_server_group_id, as: :admin
attr_accessible :template_id, :mount_template_id, :limit_id, :admin_auth_id, :directory_id, :master_relay_id,
:path_id, :logging_id, :security_id, :config_changed, :config_updated_at, :hostname, :location,
:admin_email, :fileserve, :icecast_server_group_id, :server_id, as: :admin
belongs_to :template, class_name: "JamRuby::IcecastTemplate", foreign_key: 'template_id', inverse_of: :servers

View File

@ -14,13 +14,15 @@ module JamRuby
belongs_to :sender , :inverse_of => :invited_users, :class_name => "JamRuby::User", :foreign_key => "sender_id"
# who is the invitation sent to?
validates :email, :presence => true, format: {with: VALID_EMAIL_REGEX}
validates :email, format: {with: VALID_EMAIL_REGEX}, :if => lambda { |iu| iu.email.present? }
validates :autofriend, :inclusion => {:in => [nil, true, false]}
validates :invitation_code, :presence => true
validates :note, length: {maximum: 400}, no_profanity: true # 400 == arbitrary.
validate :one_facebook_invite_per_user, :if => lambda { |iu| iu.invite_medium == FB_MEDIUM }
validate :valid_personalized_invitation
validate :not_accepted_twice
# validate :not_accepted_twice
validate :not_accepted_twice, :if => lambda { |iu| iu.email }
validate :can_invite?
after_save :track_user_progression
@ -31,6 +33,12 @@ module JamRuby
self.sender_id = nil if self.sender_id.blank? # this coercion was done just to make activeadmin work
end
FB_MEDIUM = 'facebook'
def self.facebook_invite(user)
where(:sender_id => user.id, :invite_medium => FB_MEDIUM).limit(1).first
end
def track_user_progression
self.sender.update_progression_field(:first_invited_at) unless self.sender.nil?
end
@ -54,6 +62,15 @@ module JamRuby
def invited_by_administrator?
sender.nil? || sender.admin # a nil sender can only be created by someone using jam-admin
end
def generate_signup_url
if 'development'==Rails.env
"http://jamkazamdev.local:3000/signup?invitation_code=#{self.invitation_code}"
else
"http://www.jamkazam.com/signup?invitation_code=#{self.invitation_code}"
end
end
private
def can_invite?
@ -67,5 +84,12 @@ module JamRuby
def not_accepted_twice
errors.add(:accepted, "you can only accept an invitation once") if accepted_twice
end
def one_facebook_invite_per_user
rel = InvitedUser.where(:invite_medium => FB_MEDIUM, :sender_id => self.sender_id)
rel = rel.where(['id != ?',self.id]) if self.id
errors.add(:invite_medium, "one facebook invite allowed per user") if 0 < rel.count
end
end
end

View File

@ -8,7 +8,7 @@ module JamRuby
InvitedUserMailer.welcome_betauser(invited_user).deliver
else
InvitedUserMailer.friend_invitation(invited_user).deliver
end
end if invited_user.email.present?
end
end
end
end

View File

@ -9,6 +9,7 @@ module JamRuby
belongs_to :claimed_recording, :class_name => "JamRuby::ClaimedRecording", :foreign_key => "claimed_recording_id", :inverse_of => :playing_sessions
belongs_to :claimed_recording_initiator, :class_name => "JamRuby::User", :inverse_of => :playing_claimed_recordings, :foreign_key => "claimed_recording_initiator_id"
has_one :music_session_history, :class_name => "JamRuby::MusicSessionHistory"
has_one :mount, :class_name => "JamRuby::IcecastMount", :inverse_of => :music_session, :foreign_key => 'music_session_id'
has_many :connections, :class_name => "JamRuby::Connection"

View File

@ -7,8 +7,13 @@ module JamRuby
default_scope order('created_at DESC')
belongs_to :music_session, :class_name => "JamRuby::MusicSessionHistory", :foreign_key => "music_session_id"
belongs_to :user, :class_name => "JamRuby::User", :foreign_key => "creator_id"
belongs_to(:music_session_history,
:class_name => "JamRuby::MusicSessionHistory",
:foreign_key => "music_session_id")
belongs_to(:user,
:class_name => "JamRuby::User",
:foreign_key => "creator_id")
end
end

View File

@ -15,10 +15,18 @@ module JamRuby
:foreign_key => :band_id,
:inverse_of => :music_session_history)
has_many :music_session_user_history, :class_name => "JamRuby::MusicSessionUserHistory", :foreign_key => "music_session_id"
belongs_to(:music_session,
:class_name => 'JamRuby::MusicSession',
:foreign_key => 'music_session_id')
has_many :music_session_user_histories, :class_name => "JamRuby::MusicSessionUserHistory", :foreign_key => "music_session_id"
has_many :comments, :class_name => "JamRuby::MusicSessionComment", :foreign_key => "music_session_id"
has_many :likes, :class_name => "JamRuby::MusicSessionLiker", :foreign_key => "music_session_id"
before_create :generate_share_token
SHARE_TOKEN_LENGTH = 8
SEPARATOR = '|'
def comment_count
@ -31,12 +39,12 @@ module JamRuby
def tracks
tracks = []
self.music_session_user_history.each do |msuh|
self.music_session_user_histories.each do |msuh|
user = User.find(msuh.user_id)
instruments = msuh.instruments.split(SEPARATOR)
instruments.each do |instrument|
t = Track.new
t.user = user
t.musician = user
t.instrument_id = instrument
tracks << t
end
@ -79,11 +87,33 @@ module JamRuby
.where(%Q{ music_sessions_user_history.music_session_id = '#{music_session_id}'})
end
def duration_minutes
end_time = self.session_removed_at || Time.now
(end_time - self.created_at) / 60.0
end
def music_session_user_histories
@msuh ||= JamRuby::MusicSessionUserHistory
.where(:music_session_id => self.music_session_id)
.order('created_at DESC')
end
def comments
@comments ||= JamRuby::MusicSessionComment
.where(:music_session_id => self.music_session_id)
.order('created_at DESC')
end
def likes
@likes ||= JamRuby::MusicSessionLiker
.where(:music_session_id => self.music_session_id)
end
def self.save(music_session)
session_history = MusicSessionHistory.find_by_music_session_id(music_session.id)
if session_history.nil?
session_history = MusicSessionHistory.new()
session_history = MusicSessionHistory.new
end
session_history.music_session_id = music_session.id
@ -119,15 +149,18 @@ module JamRuby
hist.end_history if hist
end
def duration_minutes
end_time = self.session_removed_at || Time.now
(end_time - self.created_at) / 60.0
def remove_non_alpha_num(token)
token.gsub(/[^0-9A-Za-z]/, '')
end
def music_session_user_histories
@msuh ||= JamRuby::MusicSessionUserHistory
.where(:music_session_id => self.music_session_id)
.order('created_at DESC')
private
def generate_share_token
self.share_token = loop do
token = SecureRandom.urlsafe_base64(SHARE_TOKEN_LENGTH, false)
token = remove_non_alpha_num(token)
token.upcase!
break token unless MusicSessionHistory.exists?(share_token: token)
end
end
end

View File

@ -5,8 +5,13 @@ module JamRuby
self.primary_key = 'id'
belongs_to :music_session, :class_name => "JamRuby::MusicSessionHistory", :foreign_key => "music_session_id"
belongs_to :user, :class_name => "JamRuby::User", :foreign_key => "liker_id"
belongs_to(:music_session_history,
:class_name => "JamRuby::MusicSessionHistory",
:foreign_key => "music_session_id")
belongs_to(:user,
:class_name => "JamRuby::User",
:foreign_key => "liker_id")
end
end

View File

@ -29,6 +29,9 @@ module JamRuby
validate :validate_part_complete
validate :validate_too_many_upload_failures
def musician
self.user
end
def can_download?(some_user)
!ClaimedRecording.find_by_user_id_and_recording_id(some_user.id, recording.id).nil?

View File

@ -156,6 +156,8 @@ module JamRuby
# the user votes to keep their tracks for this recording
def keep(user)
recorded_tracks_for_user(user).update_all(:discard => false)
User.where(:id => user.id).update_all(:first_recording_at => Time.now ) unless user.first_recording_at
end

View File

@ -7,6 +7,8 @@ module JamRuby
default_scope order('created_at ASC')
attr_accessor :musician
SOUND = %w(mono stereo)
belongs_to :connection, :class_name => "JamRuby::Connection", :inverse_of => :tracks
@ -18,6 +20,14 @@ module JamRuby
self.connection.user
end
def musician
@musician
end
def musician=(user)
@musician = user
end
def self.index(current_user, music_session_id)
query = Track
.joins(

View File

@ -147,7 +147,7 @@ module JamRuby
scope :musicians_geocoded, musicians.geocoded_users
def user_progression_fields
@user_progression_fields ||= Set.new ["first_downloaded_client_at", "first_ran_client_at", "first_music_session_at", "first_real_music_session_at", "first_good_music_session_at", "first_certified_gear_at", "first_invited_at", "first_friended_at", "first_social_promoted_at" ]
@user_progression_fields ||= Set.new ["first_downloaded_client_at", "first_ran_client_at", "first_music_session_at", "first_real_music_session_at", "first_good_music_session_at", "first_certified_gear_at", "first_invited_at", "first_friended_at", "first_recording_at", "first_social_promoted_at" ]
end
def update_progression_field(field_name, time = DateTime.now)
@ -290,8 +290,15 @@ module JamRuby
end
def recent_history
recordings = ClaimedRecording.joins(:recording).where(:recordings => {:owner_id => "#{self.id}"}).limit(10)
msh = MusicSessionHistory.where(:user_id => self.id).limit(10)
recordings = ClaimedRecording.joins(:recording)
.where(:recordings => {:owner_id => "#{self.id}"})
.order('created_at DESC')
.limit(10)
msh = MusicSessionHistory.where(:user_id => self.id)
.order('created_at DESC')
.limit(10)
recordings.concat(msh)
recordings.sort! {|a,b| b.created_at <=> a.created_at}.first(5)
end
@ -1000,10 +1007,17 @@ module JamRuby
end
end
def first_recording_at
Recording.where(:owner_id => self.id).order('created_at ASC').first.try(:created_at)
def facebook_invite!
unless iu = InvitedUser.facebook_invite(self)
iu = InvitedUser.new
iu.sender = self
iu.autofriend = true
iu.invite_medium = InvitedUser::FB_MEDIUM
iu.save
end
iu
end
# devise compatibility
#def encrypted_password

View File

@ -0,0 +1,43 @@
class GoogleAnalyticsEvent
@queue = 'google_analytics_event'
@@log = Logging.logger[GoogleAnalyticsEvent]
def self.perform(category, action)
@@log.info("starting (#{category}, #{action})")
run(category, action)
@@log.info("done (#{category}, #{action})")
end
def self.enqueue(category, event)
begin
Resque.enqueue(AudioMixer, category, event)
true
rescue
# implies redis is down. but since there is no retry logic with this, we should at least log a warn in case we've configured something wrong
@@log.warn("unable to enqueue")
false
end
end
def self.run(category, action)
raise "no google analytics tracking ID" unless APP_CONFIG.ga_ua
params = {
v: APP_CONFIG.ga_ua_version,
tid: APP_CONFIG.ga_ua,
cid: APP_CONFIG.ga_anonymous_client_id,
t: "event",
ec: category,
ea: action
}
RestClient.post(APP_CONFIG.ga_endpoint, params: params, timeout: 8, open_timeout: 8)
end
end

View File

@ -30,6 +30,11 @@ module JamRuby
"icecast-#{server_id}"
end
def self.lock_timeout
# this should be enough time to make sure the job has finished, but not so long that the system isn't recovering from a abandoned job
60
end
def self.perform(icecast_server_id)
icecast = IcecastConfigWriter.new()
icecast.icecast_server_id = icecast_server_id

View File

@ -0,0 +1,10 @@
# https://devcenter.heroku.com/articles/forked-pg-connections
Resque.before_fork do
defined?(ActiveRecord::Base) and
ActiveRecord::Base.connection.disconnect!
end
Resque.after_fork do
defined?(ActiveRecord::Base) and
ActiveRecord::Base.establish_connection
end

View File

@ -1,32 +0,0 @@
require 'json'
require 'resque'
require 'resque-retry'
require 'net/http'
require 'digest/md5'
module JamRuby
# http://blog.bignerdranch.com/1643-never-use-resque-for-serial-jobs/
# periodically scheduled to find sources that need to be brought down, or alternatively, it seems the client failed to start sourcing
class IcecastSourceCheck
@queue = :icecast_source_check
@@log = Logging.logger[IcecastSourceCheck]
def self.perform
@@log.debug("waking up")
# if we haven't seen updated_at be tickled in 5 minutes, but config_changed is still set to TRUE, this record has gotten stale
IcecastMount.find_each(:conditions => "sourced_needs_changing_at < (NOW() - interval '#{APP_CONFIG.icecast_max_sourced_changed} second')", :batch_size => 100) do |server|
server.with_lock do
IcecastConfigWriter.enqueue(server.server_id)
end
end
@@log.debug("done")
end
end
end

View File

@ -8,11 +8,17 @@ module JamRuby
# periodically scheduled to find jobs that need retrying
class AudioMixerRetry
extend Resque::Plugins::LonelyJob
@queue = :audiomixer_retry
@@log = Logging.logger[AudioMixerRetry]
def self.lock_timeout
# this should be enough time to make sure the job has finished, but not so long that the system isn't recovering from a abandoned job
120
end
def self.perform
AudioMixer.queue_jobs_needing_retry
end

View File

@ -8,11 +8,17 @@ module JamRuby
# periodically scheduled to find jobs that need retrying
class IcecastConfigRetry
extend Resque::Plugins::LonelyJob
@queue = :icecast_config_retry
@@log = Logging.logger[IcecastConfigRetry]
def self.lock_timeout
# this should be enough time to make sure the job has finished, but not so long that the system isn't recovering from a abandoned job
120
end
def self.perform
@@log.debug("waking up")

View File

@ -11,12 +11,14 @@ module JamRuby
class IcecastSourceCheck
extend Resque::Plugins::LonelyJob
@queue = :icecast_source_check
@@log = Logging.logger[IcecastSourceCheck]
def self.lock_timeout
# this should be enough time to make sure the job has finished, but not so long that the system isn't recovering from a abandoned job
120
end
def self.perform
@@log.debug("waking up")
@ -28,6 +30,7 @@ module JamRuby
@@log.debug("done")
end
def run # if we haven't seen updated_at be tickled in 5 minutes, but config_changed is still set to TRUE, this record has gotten stale
IcecastMount.find_each(lock: true, :conditions => "sourced_needs_changing_at < (NOW() - interval '#{APP_CONFIG.icecast_max_sourced_changed} second')", :batch_size => 100) do |mount|
if mount.music_session_id

View File

@ -112,4 +112,17 @@ describe ClaimedRecording do
duplicate.errors[:recording_id].should == ['has already been taken']
end
end
describe "remove_non_alpha_num" do
let(:instance) { ClaimedRecording.new }
it "removes hyphen" do
instance.remove_non_alpha_num("abc-").should == 'abc'
end
it "leaves good alone" do
instance.remove_non_alpha_num("JDnfHsimMQ").should == 'JDnfHsimMQ'
end
end
end

View File

@ -104,4 +104,28 @@ describe InvitedUser do
invited_user.valid?.should be_false
end
it 'accepts empty emails' do
user1 = FactoryGirl.create(:user)
invited_user = FactoryGirl.create(:invited_user, :sender_id => user1.id, :email => '')
expect(invited_user.valid?).to eq(true)
end
it 'accepts one facebook invite per user' do
user1 = FactoryGirl.create(:user)
invited_user = FactoryGirl.create(:invited_user, :sender_id => user1.id, :invite_medium => InvitedUser::FB_MEDIUM)
expect(invited_user.valid?).to eq(true)
invited_user.autofriend = !invited_user.autofriend
invited_user.save
expect(invited_user.valid?).to eq(true)
invited_user1 = InvitedUser.new(:email => 'foobar@example.com', :sender_id => user1.id)
invited_user1.autofriend = true
invited_user1.invite_medium = InvitedUser::FB_MEDIUM
invited_user1.save
expect(invited_user1.valid?).to eq(false)
expect(InvitedUser.facebook_invite(user1).id).to eq(invited_user.id)
user2 = FactoryGirl.create(:user)
iu = user1.facebook_invite!
expect(user1.facebook_invite!.id).to eq(iu.id)
end
end

View File

@ -230,6 +230,15 @@ describe Recording do
downloads["downloads"].length.should == 1
end
it "should mark first_recording_at" do
@recording = Recording.start(@music_session, @user)
@recording.stop
@recording.claim(@user, "Recording", "Recording Description", Genre.first, true, true)
@user.first_recording_at.should be_nil
@user.reload
@user.first_recording_at.should_not be_nil
end
describe "chance for everyone to keep or discard" do
before(:each) do
@user2 = FactoryGirl.create(:user)

6
runadmin Executable file
View File

@ -0,0 +1,6 @@
#!/bin/bash
pushd admin
# run jam-admin rails server
bundle exec rails s
popd

6
runjobs Executable file
View File

@ -0,0 +1,6 @@
#!/bin/bash
pushd web
# run all_jobs rake task; this waits on new jobs from the resque queue, i.e., audiomixer, icecast, etc
bundle exec rake all_jobs
popd

32
runtests Executable file
View File

@ -0,0 +1,32 @@
#!/bin/bash
set -e
echo ""
pushd ruby > /dev/null
echo "RUNNING RUBY TESTS"
bundle exec rspec
popd > /dev/null
echo ""
pushd web > /dev/null
echo "RUNNING WEB TESTS"
bundle exec rspec
popd > /dev/null
echo ""
pushd admin > /dev/null
echo "RUNNING ADMIN TESTS"
bundle exec rspec
popd > /dev/null
echo ""
pushd websocket-gateway > /dev/null
echo "RUNNING WEBSOCKET-GATEWAY TESTS"
bundle exec rspec
popd > /dev/null
echo "TESTS PASSED"

View File

@ -17,6 +17,7 @@ else
gem 'jampb', "0.1.#{ENV["BUILD_NUMBER"]}"
gem 'jam_ruby', "0.1.#{ENV["BUILD_NUMBER"]}"
gem 'jam_websockets', "0.1.#{ENV["BUILD_NUMBER"]}"
ENV['NOKOGIRI_USE_SYSTEM_LIBRARIES'] ||= "true"
end
gem 'builder'
gem 'rails', '>=3.2.11'
@ -105,6 +106,7 @@ group :test, :cucumber do
# gem 'rb-fsevent', '0.9.1', :require => false
# gem 'growl', '1.0.3'
gem 'poltergeist'
gem 'resque_spec'
end

View File

@ -198,7 +198,7 @@
}
else {
band.id = bandId;
rest.updateBand(band, bandId).done(function(response) {
rest.updateBand(band).done(function(response) {
createBandInvitations(band.id, function() {
context.location = "#/bandProfile/" + band.id;
});

View File

@ -200,8 +200,8 @@
invitationDialog.showGoogleDialog();
});
$('div[layout-id="createSession"] .btn-facebook-invitation').click(function() {
invitationDialog.showFacebookDialog();
$('div[layout-id="createSession"] .btn-facebook-invitation').click(function(e) {
invitationDialog.showFacebookDialog(e);
});
$('#friend-input').focus(function() { $(this).val(''); })

View File

@ -30,6 +30,53 @@
accept : "Accept"
};
var recordingActions = {
make : "Make",
share : "Share"
};
var recordingShareTypes = {
facebook : "Facebook",
syndicationWidget : "SyndWidget",
syndicationUrl: "SyndURL"
};
var recordingPlayActions = {
website : "Website",
client : "Client",
facebook : "Facebook",
syndicationWidget : "SyndWidget",
syndicationUrl: "SyndURL"
};
var sessionPlayActions = {
website : "Website",
client : "Client",
facebook : "Facebook",
syndicationWidget : "SyndWidget",
syndicationUrl: "SyndURL"
};
var userLabels = {
registeredUser : "RegisteredUser",
visitor: "Visitor"
};
var bandActions = {
create : "Create",
join : "Join",
session : "Session",
recording : "Recording"
};
var jkSocialTargets = {
musician : 'Musician',
band : 'Band',
fan : 'Fan',
recording : 'Recording',
session : 'Session'
};
var categories = {
register : "Register",
download : "DownloadClient",
@ -38,7 +85,15 @@
sessionMusicians : "SessionMusicians",
invite : "Invite",
findSession : "FindSession",
friendConnect : "Connect"
friendConnect : "Connect",
recording : "Recording",
recordingPlay : "RecordingPlay",
sessionPlay : "SessionPlay",
band : "Band",
jkLike : 'jkLike',
jkFollow : 'jkFollow',
jkFavorite : 'jkFavorite',
jkComment : 'jkComment'
};
@ -161,10 +216,56 @@
context.ga('send', 'event', categories.friendConnect, friendConnectType);
}
// when someone keeps a recording
function trackMakeRecording() {
context.ga('send', 'event', categories.recording, recordingActions.make);
}
// when someone shares a recording
function trackShareRecording(shareType) {
assertOneOf(shareType, recordingShareTypes);
context.ga('send', 'event', categories.recording, recordingActions.share, shareType);
}
// when someone plays a recording
function trackRecordingPlay(recordingAction) {
assertOneOf(recordingAction, recordingPlayActions);
var label = JK.currentUserId ? userLabels.registeredUser : userLabels.visitor;
context.ga('send', 'event', categories.recordingPlay, recordingAction, label);
}
// when someone plays a live session broadcast
function trackSessionPlay(recordingAction) {
assertOneOf(recordingAction, sessionPlayActions);
var label = JK.currentUserId ? userLabels.registeredUser : userLabels.visitor;
context.ga('send', 'event', categories.sessionPlay, recordingAction, label);
}
function trackBand(bandAction) {
assertOneOf(bandAction, bandActions);
context.ga('send', 'event', categories.band, bandAction);
}
function trackJKSocial(category, target) {
assertOneOf(category, categories);
assertOneOf(target, jkSocialTargets);
context.ga('send', 'event', category, target);
}
var GA = {};
GA.Categories = categories;
GA.SessionCreationTypes = sessionCreationTypes;
GA.InvitationTypes = invitationTypes;
GA.FriendConnectTypes = friendConnectTypes;
GA.RecordingActions = recordingActions;
GA.BandActions = bandActions;
GA.JKSocialTargets = jkSocialTargets;
GA.trackRegister = trackRegister;
GA.trackDownload = trackDownload;
GA.trackFTUECompletion = trackFTUECompletion;
@ -174,6 +275,13 @@
GA.trackFindSessions = trackFindSessions;
GA.virtualPageView = virtualPageView;
GA.trackFriendConnect = trackFriendConnect;
GA.trackMakeRecording = trackMakeRecording;
GA.trackShareRecording = trackShareRecording;
GA.trackRecordingPlay = trackRecordingPlay;
GA.trackSessionPlay = trackSessionPlay;
GA.trackBand = trackBand;
GA.trackJKSocial = trackJKSocial;
context.JK.GA = GA;

View File

@ -1,5 +1,4 @@
(function(context,$) {
"use strict";
context.JK = context.JK || {};
context.JK.InvitationDialog = function(app) {
@ -7,6 +6,7 @@
var rest = context.JK.Rest();
var waitForUserToStopTypingTimer;
var sendingEmail = false;
var fbInviteURL_ = null;
function trackMetrics(emails, googleInviteCount) {
var allInvitations = emails.length; // all email invites, regardless of how they got in the form
@ -152,49 +152,114 @@
};
window._oauth_win = window.open("/auth/google_login", "_blank", "height=500,width=500,menubar=no,resizable=no,status=no");
}
//////////////
// FB handlers
function showFacebookDialog() {
/*
$('#invitation-textarea-container').hide();
$('#invitation-checkbox-container').show();
$('#btn-send-invitation').hide();
$('#btn-next-invitation').show();
invitationDialog.showDialog();
$('#invitation-checkboxes').html('<div style="text-align: center; margin-top: 100px;">Loading your contacts...</div>');
*/
window._oauth_callback = function() {
window._oauth_win.close();
window._oauth_win = null;
window._oauth_callback = null;
/*
$.ajax({
type: "GET",
url: "/gmail_contacts",
success: function(response) {
$('#invitation-checkboxes').html('');
for (var i in response) {
$('#invitation-checkboxes').append("<label><input type='checkbox' class='invitation-checkbox' data-email='" + response[i] + "' /> " + response[i] + "</label>");
}
$('.invitation-checkbox').change(function() {
var checkedBoxes = $('.invitation-checkbox:checkbox:checked');
var emails = '';
for (var i = 0; i < checkedBoxes.length; i++) {
emails += $(checkedBoxes[i]).data('email') + ', ';
}
emails = emails.replace(/, $/, '');
$('#txt-emails').val(emails);
});
},
error: function() {
$('#invitation-checkboxes').html("Load failed");
}
});
*/
};
window._oauth_win = window.open("/auth/facebook_login", "_blank", "height=500,width=500,menubar=no,resizable=no,status=no");
// Additional initialization code such as adding Event Listeners goes here
function handle_fblogin_response(response) {
if (response.status === 'connected') {
// the user is logged in and has authenticated your
// app, and response.authResponse supplies
// the user's ID, a valid access token, a signed
// request, and the time the access token
// and signed request each expire
var uid = response.authResponse.userID;
var accessToken = response.authResponse.accessToken;
window.fb_logged_in_state = "connected";
} else if (response.status === 'not_authorized') {
// the user is logged in to Facebook,
// but has not authenticated your app
// TODO: popup authorization dialog
window.fb_logged_in_state = "not_authorized";
}
else {
// the user isn't logged in to Facebook.
window.fb_logged_in_state = "not_logged_in";
}
}
this.fb_login = function() {
FB.login(function(response) {
handle_fblogin_response(response);
}, {scope:'publish_stream'});
}
function fbInviteURL() {
if (fbInviteURL_ === null) {
$.ajax({
type: "GET",
async: false,
url: '/api/invited_users/facebook',
success: function(response) {
fbInviteURL_ = response['signup_url'];
},
error: app.ajaxError
});
}
return fbInviteURL_;
}
function show_feed_dialog() {
var obj = {
method: 'feed',
link: fbInviteURL(),
picture: 'http://jamkazam.com/assets/logo.png',
name: 'Join me on JamKazam',
caption: 'Play live music in real-time sessions with others over the Internet, as if in the same room.',
description: '',
actions: [{ name: 'Signup', link: fbInviteURL() }]
};
function fbFeedDialogCallback(response) {
//console.log("feedback dialog closed: " + response['post_id'])
}
FB.ui(obj, fbFeedDialogCallback);
}
function showFacebookDialog(evt) {
if (!(evt === undefined)) evt.stopPropagation();
var fb_state = window.fb_logged_in_state;
if (fb_state == "connected") {
show_feed_dialog();
} else if (fb_state == "not_authorized") {
this.fb_login();
} else {
this.fb_login();
}
}
function callFB(fbAppID){
var fbAppID_ = fbAppID;
window.fbAsyncInit = function() {
FB.init({
appId : fbAppID_,
// channelUrl : '//WWW.YOUR_DOMAIN.COM/channel.html',
status : true, // check the login status upon init?
cookie : true, // set sessions cookies to allow server to access the session?
xfbml : true, // parse XFBML tags on this page?
oauth : true, // enable OAuth 2.0
});
// listen to see if the user is known/logged in
FB.getLoginStatus(function(response) {
handle_fblogin_response(response);
});
};
// Load the SDK Asynchronously
(function(d){
var js, id = 'facebook-jssdk'; if (d.getElementById(id)) {return;}
js = d.createElement('script'); js.id = id; js.async = true;
js.src = "//connect.facebook.net/en_US/all.js";
d.getElementsByTagName('head')[0].appendChild(js);
}(document));
}
// END FB handlers
//////////////
function clearTextFields() {
$('#txt-emails').val('').removeData('google_invite_count');
$('#txt-message').val('');
@ -211,16 +276,17 @@
registerEvents(false);
}
function initialize(){
function initialize(fbAppID){
var dialogBindings = {
'beforeShow' : beforeShow,
'afterHide': afterHide
};
app.bindDialog('inviteUsers', dialogBindings);
callFB(fbAppID);
};
this.initialize = initialize;
this.showEmailDialog = showEmailDialog;
this.showGoogleDialog = showGoogleDialog;
@ -228,4 +294,4 @@
}
return this;
})(window,jQuery);
})(window,jQuery);

View File

@ -35,7 +35,6 @@
}
function updateSession(id, newSession, onSuccess) {
logger.debug('Rest.updateSession');
return $.ajax('/api/sessions/' + id, {
type: "PUT",
data : newSession,
@ -44,6 +43,56 @@
});
}
function addSessionComment(sessionId, userId, comment) {
return $.ajax({
url: '/api/sessions/' + sessionId + "/comments",
type: "POST",
data : JSON.stringify({"comment": comment, "user_id": userId}),
dataType : 'json',
contentType: 'application/json'
});
}
function addSessionLike(sessionId, userId) {
return $.ajax({
url: '/api/sessions/' + sessionId + "/likes",
type: "POST",
data : JSON.stringify({"user_id": userId}),
dataType : 'json',
contentType: 'application/json'
});
}
function addRecordingComment(recordingId, userId, comment) {
return $.ajax({
url: '/api/recordings/' + recordingId + "/comments",
type: "POST",
data : JSON.stringify({"comment": comment, "user_id": userId}),
dataType : 'json',
contentType: 'application/json'
});
}
function addRecordingLike(recordingId, userId) {
return $.ajax({
url: '/api/recordings/' + recordingId + "/likes",
type: "POST",
data : JSON.stringify({"user_id": userId}),
dataType : 'json',
contentType: 'application/json'
});
}
function addRecordingPlay(recordingId, userId) {
return $.ajax({
url: '/api/recordings/' + recordingId + "/plays",
type: "POST",
data : JSON.stringify({"user_id": userId}),
dataType : 'json',
contentType: 'application/json'
});
}
function getBand(bandId) {
return $.ajax({
type: "GET",
@ -55,7 +104,7 @@
}
function createBand(band) {
return $.ajax({
var deferred = $.ajax({
type: "POST",
dataType: "json",
url: '/api/bands',
@ -63,18 +112,13 @@
processData: false,
data: JSON.stringify(band)
});
}
function updateBand(band, bandId) {
logger.debug("bandId=" + bandId);
return $.ajax({
type: "POST",
dataType: "json",
url: '/api/bands/' + bandId,
contentType: 'application/json',
processData: false,
data: JSON.stringify(band)
deferred.done(function() {
context.JK.GA.trackBand(context.JK.GA.BandActions.create);
context.JK.GA.trackBand(context.JK.GA.BandActions.join);
});
return deferred;
}
function updateBand(band) {
@ -105,14 +149,22 @@
}
function updateBandInvitation(bandId, invitationId, isAccepted) {
return $.ajax({
var deferred = $.ajax({
type: "POST",
dataType: "json",
url: '/api/bands/' + bandId + "/invitations/" + invitationId,
contentType: 'application/json',
processData: false,
data: JSON.stringify({"accepted": isAccepted})
});
})
if(isAccepted) {
deferred.done(function() {
context.JK.GA.trackBand(context.JK.GA.BandActions.join);
})
}
return deferred;
}
function removeBandMember(bandId, userId) {
@ -705,6 +757,11 @@
this.getBandFollowing = getBandFollowing;
this.getBands = getBands;
this.updateSession = updateSession;
this.addSessionComment = addSessionComment;
this.addSessionLike = addSessionLike;
this.addRecordingComment = addRecordingComment;
this.addRecordingLike = addRecordingLike;
this.addRecordingPlay = addRecordingPlay;
this.getSession = getSession;
this.getClientDownloads = getClientDownloads;
this.createInvitation = createInvitation;

View File

@ -207,6 +207,7 @@
updateFollowingCount(1);
setFollowing(true);
configureFollowingButton();
context.JK.GA.trackJKSocial(context.JK.GA.Categories.jkFollow, isMusician() ? context.JK.GA.JKSocialTargets.musician : context.JK.GA.JKSocialTargets.fan);
})
.fail(app.ajaxError);
}
@ -605,6 +606,7 @@
logger.debug("following band " + bandId);
updateBandFollowingCount(bandId, 1); // increase counter
configureBandFollowingButton(true, bandId);
context.JK.GA.trackJKSocial(context.JK.GA.Categories.jkFollow, context.JK.GA.JKSocialTargets.band);
})
.fail(app.ajaxError);
}

View File

@ -127,7 +127,8 @@
is_downloadable: is_downloadable
})
.done(function() {
app.layout.closeDialog('recordingFinished')
app.layout.closeDialog('recordingFinished');
GA.trackMakeRecording();
})
.fail(function(jqXHR) {
if(jqXHR.status == 422) {

View File

@ -10,6 +10,10 @@
}
function showDialog() {
app.layout.showDialog('share-dialog');
}
/*function showEmailDialog() {
$('#invitation-dialog').show();
$('#invitation-textarea-container').show();
@ -94,8 +98,8 @@
app.bindDialog('shareSessionRecording', dialogBindings);
};
this.initialize = initialize;
this.showDialog = showDialog;
}
return this;

View File

@ -442,6 +442,11 @@
invitationDialog.showGoogleDialog();
return false;
});
$('#sidebar-div .btn-facebook-invitation').click(function(evt) {
invitationDialog.showFacebookDialog(evt);
return false;
});
}
function registerFriendUpdate() {

View File

@ -37,6 +37,10 @@
invitationDialog.showEmailDialog();
});
$('.invite-friends .facebook-invite a').on('click', function(e) {
invitationDialog.showFacebookDialog(e);
});
$('#header-avatar').on('avatar_changed', function(event, newAvatarUrl) {
updateAvatar(newAvatarUrl);
event.preventDefault();

View File

@ -430,7 +430,7 @@ ul.shortcuts {
color:#FFCC00;
}
li.google-invite, li.email-invite {
li.google-invite, li.email-invite, li.facebook-invite {
padding-left:9px;
}

View File

@ -150,64 +150,60 @@ textarea {
/* Start of Jeff's common.css file */
body {
font-family: Raleway, Arial, Helvetica, sans-serif;
font-size: 14px;
font-weight: 300;
color: #FFF;
padding:3% 6%;
background-color: #262626;
position:relative;
font-family: Raleway, Arial, Helvetica, sans-serif;
font-size: 14px;
font-weight: 300;
color: #FFF;
padding:3% 6%;
background-color: #262626;
position:relative;
}
h1, h2, h3, h4, h5, h6, p, div, table, form, img, tr, td, th {
margin:0;
border:0;
padding:0;
margin:0;
border:0;
padding:0;
}
a {
display:inline-block;
}
a {display:inline-block;}
a img {border:none;}
.clearall {clear:both;}
.clearleft {clear:left;}
small, .small {font-size:11px;}
.bold {font-weight:bold;}
small, .small {
font-size:11px;
}
.bold {
font-weight:bold;
}
.button-grey {
margin:0px 8px 0px 8px;
background-color:#666;
border: solid 1px #868686;
outline:solid 2px #666;
padding:3px 10px;
font-family:raleway;
font-size:12px;
font-weight:300;
cursor:pointer;
color:#ccc;
text-decoration:none;
margin:0px 8px 0px 8px;
background-color:#666;
border: solid 1px #868686;
outline:solid 2px #666;
padding:3px 10px;
font-family:raleway;
font-size:12px;
font-weight:300;
cursor:pointer;
color:#ccc;
text-decoration:none;
}
.button-grey:hover {
background-color:#999;
color:#FFF;
background-color:#999;
color:#FFF;
text-decoration:none;
}
.button-orange {
margin:0px 8px 0px 8px;
background-color: #ED3618;
border: solid 1px #F27861;
outline: solid 2px #ED3618;
padding:3px 10px;
font-family:raleway;
font-size:12px;
font-weight:300;
cursor:pointer;
color:#FC9;
text-decoration:none;
margin:0px 8px 0px 8px;
background-color: #ED3618;
border: solid 1px #F27861;
outline: solid 2px #ED3618;
padding:3px 10px;
font-family:raleway;
font-size:12px;
font-weight:300;
cursor:pointer;
color:#FC9;
text-decoration:none;
}
.smallbutton {
@ -216,8 +212,8 @@ small, .small {
}
.button-orange:hover {
background-color:#f16750;
color:#FFF;
background-color:#f16750;
color:#FFF;
text-decoration:none;
}
@ -234,17 +230,6 @@ small, .small {
background-color:#666;
}
a img {
border:none;
}
.clearall {
clear:both;
}
.clearleft {
clear:left;
}
.f8 {font-size:8px !important}
.f9 {font-size:9px !important}
.f10 {font-size:10px !important}
@ -337,29 +322,29 @@ a img {
}
.op50 {
opacity: .5;
-ms-filter: "alpha(opacity=50)";
opacity: .5;
-ms-filter: "alpha(opacity=50)";
}
.op30 {
opacity: .3;
-ms-filter: "alpha(opacity=30)";
opacity: .3;
-ms-filter: "alpha(opacity=30)";
}
.nowrap {
display:inline-block;
white-space:nowrap;
display:inline-block;
white-space:nowrap;
}
.overlay {
display:none;
position:fixed;
width:100%;
height:100%;
top:0px;
left:0px;
position:fixed;
width:100%;
height:100%;
top:0px;
left:0px;
z-index: 999;
background-image:url('/assets/shared/bkg_overlay.png');
background-image:url('/assets/shared/bkg_overlay.png');
}
.overlay-small {

View File

@ -3,6 +3,15 @@ body.widgets {
padding:20px;
}
h3 {
font-size:20px;
font-weight:normal;
}
.share-overlay {
}
.widget {
width:430px;
height:180px;

View File

@ -58,12 +58,19 @@ body.web {
padding:25px;
padding-top:0px;
line-height:130%;
}
h2 {
font-size:18px !important;
line-height:normal;
color:#fff;
}
a {
text-decoration: underline !important;
}
.landing-sidebar h2 {
font-size:18px !important;
line-height:normal;
color:#fff;
em {
font-style: italic !important;
}
}
.landing-band {

View File

@ -1,2 +1,19 @@
/*.session-controls {
background-color:#471f18;
}
.session-controls.inprogress {
background-color:#4C742E;
}
.session-status-ended, .session-status {
float:left;
font-size:18px;
}
.session-status-inprogress {
float:left;
font-size:15px;
color:#cccc00;
margin-left:20px;
}*/

View File

@ -10,7 +10,11 @@ class ApiInvitedUsersController < ApiController
end
def show
@invited_user = InvitedUser.find(params[:id])
if 'facebook' == params[:id]
@invited_user = current_user.facebook_invite!
else
@invited_user = InvitedUser.find(params[:id])
end
end
def create

View File

@ -89,7 +89,6 @@ class ApiMusicSessionsController < ApiController
end
def participant_create
@connection = MusicSessionManager.new.participant_create(
current_user,
params[:id],
@ -243,14 +242,17 @@ class ApiMusicSessionsController < ApiController
def add_comment
if params[:id].blank?
render :json => { :message => "Session ID is required" }, :status => 400
return
end
if params[:user_id].blank?
render :json => { :message => "User ID is required" }, :status => 400
return
end
if params[:comment].blank?
render :json => { :message => "Comment is required" }, :status => 400
return
end
comment = MusicSessionComment.new
@ -262,18 +264,17 @@ class ApiMusicSessionsController < ApiController
if comment.errors.any?
render :json => { :message => "Unexpected error occurred" }, :status => 500
return
else
render :json => {}, :status => 201
return
end
end
def add_like
if params[:id].blank?
render :json => { :message => "Session ID is required" }, :status => 400
end
if params[:user_id].blank?
render :json => { :message => "User ID is required" }, :status => 400
return
end
liker = MusicSessionLiker.new
@ -284,11 +285,17 @@ class ApiMusicSessionsController < ApiController
if liker.errors.any?
render :json => { :message => "Unexpected error occurred" }, :status => 500
return
else
render :json => {}, :status => 201
return
end
end
def history_show
@history = MusicSessionHistory.find_by_music_session_id(params[:id])
end
def claimed_recording_start
@music_session.claimed_recording_start(current_user, ClaimedRecording.find(params[:claimed_recording_id]))

View File

@ -83,14 +83,17 @@ class ApiRecordingsController < ApiController
def add_comment
if params[:id].blank?
render :json => { :message => "Recording ID is required" }, :status => 400
return
end
if params[:user_id].blank?
render :json => { :message => "User ID is required" }, :status => 400
return
end
if params[:comment].blank?
render :json => { :message => "Comment is required" }, :status => 400
return
end
comment = RecordingComment.new
@ -102,18 +105,17 @@ class ApiRecordingsController < ApiController
if comment.errors.any?
render :json => { :message => "Unexpected error occurred" }, :status => 500
return
else
render :json => {}, :status => 201
return
end
end
def add_like
if params[:id].blank?
render :json => { :message => "Recording ID is required" }, :status => 400
end
if params[:user_id].blank?
render :json => { :message => "User ID is required" }, :status => 400
return
end
liker = RecordingLiker.new
@ -124,18 +126,17 @@ class ApiRecordingsController < ApiController
if liker.errors.any?
render :json => { :message => "Unexpected error occurred" }, :status => 500
return
else
render :json => {}, :status => 201
return
end
end
def add_play
if params[:id].blank?
render :json => { :message => "Recording ID is required" }, :status => 400
end
if params[:user_id].blank?
render :json => { :message => "User ID is required" }, :status => 400
return
end
play = RecordingPlay.new
@ -146,8 +147,10 @@ class ApiRecordingsController < ApiController
if play.errors.any?
render :json => { :message => "Unexpected error occurred" }, :status => 500
return
else
render :json => {}, :status => 201
return
end
end

View File

@ -29,7 +29,7 @@ class UsersController < ApplicationController
@invited_user = load_invited_user(params)
if !@invited_user.nil? && @invited_user.accepted
if !@invited_user.nil? && @invited_user.email && @invited_user.accepted
# short-circuit out if this invitation is already accepted
render "already_signed_up", :layout => 'landing'
return

View File

@ -1,3 +1,4 @@
object @invited_user
attributes :id, :created_at, :updated_at, :email, :note, :accepted
attributes :id, :created_at, :updated_at, :email, :note, :accepted

View File

@ -1,3 +1,5 @@
object @invited_user
extends "api_invited_users/invited_user"
node :signup_url do @invited_user.generate_signup_url end

View File

@ -0,0 +1,19 @@
object @history
attributes :music_session_id, :description, :genres
child(:user => :creator) {
attributes :name, :photo_url
}
child(:band => :band) {
attributes :name, :photo_url
}
child(:music_session_user_histories => :users) {
attributes :instruments
child(:user => :user) {
attributes :name, :photo_url
}
}

View File

@ -73,4 +73,4 @@ child({:mount => :mount}, :if => lambda { |music_session| music_session.fan_acce
node(:mime_type) { |mount| mount.resolve_string(:mime_type) }
node(:bitrate) { |mount| mount.resolve_string(:bitrate) }
node(:subtype) { |mount| mount.resolve_string(:subtype) }
}
}

View File

@ -98,16 +98,14 @@
</div>
<div class="right mt5 ml5">E-mail</div>
</div>
<!--
<div class="left mr20">
<div class="left">
<a id="btn-facebook-invitation">
<a class="btn-facebook-invitation">
<%= image_tag("content/icon_facebook.png", :size => "24x24", :align => "absmiddle") %>
</a>
</div>
<div class="right mt5 ml5">Facebook</div>
</div>
-->
<!-- <div class="left mr20">
<div class="left">
<a href="/#/createSession" title="This feature is not yet available.">

View File

@ -1,4 +1,4 @@
m<!-- Profile -->
<!-- Profile -->
<div layout="screen" layout-id="profile" layout-arg="id" class="screen secondary">
<div class="content-head">
<div class="content-icon">

View File

@ -137,7 +137,7 @@
<%= render "addNewGear" %>
<%= render "error" %>
<%= render "sessionSettings" %>
<%= render "shareDialog" %>
<%= render :partial => "shareDialog" %>
<!-- Track Template -->
<script type="text/template" id="template-session-track">

View File

@ -1,5 +1,5 @@
<!-- Share dialog -->
<div class="dialog" layout="dialog" layout-id="share-dialog" style="width:800px; height:auto;">
<div class="dialog share-overlay" layout="dialog" layout-id="share-dialog" style="width:800px; height:auto;">
<div class="content-head"><h1>share this session</h1></div>
<div class="dialog-inner">
<div class="right"> <a class="button-orange" layout-action="close">X CLOSE</a></div>
@ -19,7 +19,8 @@
</td>
<td valign="top" width="48%">
<div class="ml10">
<h3>Share a Link:</h3><br />http://jamkazam.com/TD48JKZ1<br /><br />
<h3>Share a Link:</h3><br />
<%= true ? '' : "#{root_url}#{share_token}" %>
<div class="right"><a class="button-orange">COPY LINK</a></div>
</div>
</td>
@ -94,7 +95,7 @@
<a href="javascript:void(0)" class="widget-playbutton" title="play"></a>
<!-- song title -->
<div class="widget-title">You Hurt Me Bad</div>
<!-- band member avatars -->
<!-- avatars -->
<div class="widget-members">
<div class="widget-avatar-small"><%= image_tag "shared/avatar_david.jpg", :alt => "" %></div>
<div class="widget-avatar-small"><%= image_tag "shared/avatar_silverfox.jpg", :alt => "" %></div>

View File

@ -1,214 +1,222 @@
<div id="sidebar-div" layout="sidebar" class="sidebar" style="display:none;">
<div layout-sidebar-expander="visible" class="expander visible">
<p>
<%= image_tag "sidebar/expand_arrows_right.jpg" %>
</p>
</div>
<div layout-sidebar-expander="hidden" class="expander hidden">
<p>
<%= image_tag "sidebar/expand_arrows_left.jpg" %>
</p>
</div>
<div layout-sidebar-expander="visible" class="expander visible">
<p>
<%= image_tag "sidebar/expand_arrows_right.jpg" %>
</p>
</div>
<div layout-sidebar-expander="hidden" class="expander hidden">
<p>
<%= image_tag "sidebar/expand_arrows_left.jpg" %>
</p>
</div>
<!-- Search Box -->
<div class="search">
<div layout-panel="header" class="panel-header">
<h2>search</h2>
<!-- Search Box -->
<div class="search">
<div layout-panel="header" class="panel-header">
<h2>search</h2>
</div>
<div class="searchbox">
<form id="searchForm">
<input id="search-input" autocomplete="off" type="text" name="search" placeholder="enter search terms" />
</form>
<div id="sidebar-search-header" style="margin: 4px 4px 8px 8px">
<div class="left">
<strong><a id="sidebar-search-expand" style="color:#fff; text-decoration:underline">&laquo;&nbsp;EXPAND</a></strong>
</div>
<div class="searchbox">
<form id="searchForm">
<input id="search-input" autocomplete="off" type="text" name="search" placeholder="enter search terms" />
</form>
<div id="sidebar-search-header" style="margin: 4px 4px 8px 8px">
<!-- search filter dropdown -->
<div class="right">
Show:&nbsp;<%= select_tag(Search::SEARCH_TEXT_TYPE_ID, options_for_select(Search::SEARCH_TEXT_TYPES.collect { |ii| [ii.to_s.titleize, ii] })) %>
</div>
</div>
</div>
<div style="clear:both;"></div><br />
<!-- border between header and beginning of search results -->
<div class="sidebar-search-result"></div>
<div id="sidebar-search-results" class="results-wrapper">
</div>
</div>
<!-- Friends -->
<div layout="panel" layout-id="panelFriends">
<div layout-panel="collapsed">
F
</div>
<div layout-panel="expanded" class="panel expanded">
<div layout-panel="header" class="panel-header">
<h2>friends<div id="sidebar-friend-count" class="badge">0</div></h2>
</div>
<div layout-panel="contents" class="panelcontents">
<ul id="sidebar-friend-list">
<li class="invite-friend-row">
<div class="friend-name">
Invite More Friends
</div>
<br clear="all"/>
<div class="invitation-button-holder">
<div class="left mr20">
<div class="left">
<strong><a id="sidebar-search-expand" style="color:#fff; text-decoration:underline">&laquo;&nbsp;EXPAND</a></strong>
<a class="btn-email-invitation">
<%= image_tag("content/icon_gmail.png", :size => "24x24", :align => "absmiddle") %>
</a>
</div>
<!-- search filter dropdown -->
<div class="right">
Show:&nbsp;<%= select_tag(Search::SEARCH_TEXT_TYPE_ID, options_for_select(Search::SEARCH_TEXT_TYPES.collect { |ii| [ii.to_s.titleize, ii] })) %>
<div class="right mt5 ml5">E-mail</div>
</div>
<div class="left left">
<div class="left">
<a class="btn-gmail-invitation">
<%= image_tag("content/icon_google.png", :size => "24x24", :align => "absmiddle") %>
</a>
</div>
</div>
</div>
<div style="clear:both;"></div><br />
<!-- border between header and beginning of search results -->
<div class="sidebar-search-result"></div>
<div id="sidebar-search-results" class="results-wrapper">
</div>
</div>
<!-- Friends -->
<div layout="panel" layout-id="panelFriends">
<div layout-panel="collapsed">
F
</div>
<div layout-panel="expanded" class="panel expanded">
<div layout-panel="header" class="panel-header">
<h2>friends<div id="sidebar-friend-count" class="badge">0</div></h2>
</div>
<div layout-panel="contents" class="panelcontents">
<ul id="sidebar-friend-list">
<li class="invite-friend-row">
<div class="friend-name">
Invite More Friends
</div>
<br clear="all"/>
<div class="invitation-button-holder">
<div class="left mr20">
<div class="left">
<a class="btn-email-invitation">
<%= image_tag("content/icon_gmail.png", :size => "24x24", :align => "absmiddle") %>
</a>
</div>
<div class="right mt5 ml5">E-mail</div>
</div>
<div class="left left">
<div class="left">
<a class="btn-gmail-invitation">
<%= image_tag("content/icon_google.png", :size => "24x24", :align => "absmiddle") %>
</a>
</div>
<div class="right mt5 ml5">Google+</div>
</div>
</div>
<br clear="all" />
</li>
</ul>
</div>
</div>
</div>
<!-- Chat -->
<div layout="panel" layout-id="panelChat">
<div layout-panel="collapsed">
C
</div>
<div layout-panel="expanded" class="panel expanded">
<div layout-panel="header" class="panel-header">
<h2>chat<div id="sidebar-chat-count" class="badge">0</div></h2>
</div>
<div layout-panel="contents" class="panelcontents">
<!-- chat message input -->
<div class="chat-fixed">
<input id="chat-input" type="text" placeholder="enter message" /><br />
<!-- send to box -->
<div class="chat-select">Send to:
<select id="sidebar-chat-friend-list">
<option>Everyone</option>
<option>All Musicians</option>
<option>All Fans</option>
</select>
</div>
<div class="right mt5 ml5">Google+</div>
</div>
<div class="left left">
<div class="left">
<a class="btn-facebook-invitation">
<%= image_tag("content/icon_facebook.png", :size => "24x24", :align => "absmiddle") %>
</a>
</div>
<ul id="sidebar-chat-list">
</ul>
<div class="right mt5 ml5">Facebook</div>
</div>
</div>
</div>
<br clear="all" />
</li>
</ul>
</div>
</div>
</div>
<!-- Notifications -->
<div layout="panel" layout-id="panelNotifications">
<div layout-panel="collapsed">
N
</div>
<div layout-panel="expanded" class="panel expanded">
<div layout-panel="header" class="panel-header">
<h2>notifications<div id="sidebar-notification-count" class="badge">0</div></h2>
</div>
<div layout-panel="contents" class="panelcontents">
<ul id="sidebar-notification-list">
</ul>
</div>
</div>
<!-- Chat -->
<div layout="panel" layout-id="panelChat">
<div layout-panel="collapsed">
C
</div>
<div layout-panel="expanded" class="panel expanded">
<div layout-panel="header" class="panel-header">
<h2>chat<div id="sidebar-chat-count" class="badge">0</div></h2>
</div>
<div layout-panel="contents" class="panelcontents">
<!-- chat message input -->
<div class="chat-fixed">
<input id="chat-input" type="text" placeholder="enter message" /><br />
<!-- send to box -->
<div class="chat-select">Send to:
<select id="sidebar-chat-friend-list">
<option>Everyone</option>
<option>All Musicians</option>
<option>All Fans</option>
</select>
</div>
</div>
<ul id="sidebar-chat-list">
</ul>
</div>
</div>
</div>
<!-- Notifications -->
<div layout="panel" layout-id="panelNotifications">
<div layout-panel="collapsed">
N
</div>
<div layout-panel="expanded" class="panel expanded">
<div layout-panel="header" class="panel-header">
<h2>notifications<div id="sidebar-notification-count" class="badge">0</div></h2>
</div>
<div layout-panel="contents" class="panelcontents">
<ul id="sidebar-notification-list">
</ul>
</div>
</div>
</div>
</div>
<!-- Search result template -->
<script type="text/template" id="template-musicians-sidebar-search-result">
<div user-id="{userId}" class="sidebar-search-result">
<a class="avatar-small"><img src="{avatar_url}" /></a>
<div class="result-name"><a href="{profile_url}">{userName}</a><br />
<span class="result-location">{location}</span>
</div><br />
<a class="btn-connect-friend button-orange smallbutton right">CONNECT</a>
<br clear="all" />
</div>
<div user-id="{userId}" class="sidebar-search-result">
<a class="avatar-small"><img src="{avatar_url}" /></a>
<div class="result-name"><a href="{profile_url}">{userName}</a><br />
<span class="result-location">{location}</span>
</div><br />
<a class="btn-connect-friend button-orange smallbutton right">CONNECT</a>
<br clear="all" />
</div>
</script>
<script type="text/template" id="template-bands-sidebar-search-result">
<div band-id="{bandId}" class="sidebar-search-result">
<a class="avatar-small"><img src="{avatar_url}" /></a>
<div class="result-name"><a href="{band_url}">{bandName}</a><br />
<span class="result-location">{location}</span>
</div><br />
</div>
<div band-id="{bandId}" class="sidebar-search-result">
<a class="avatar-small"><img src="{avatar_url}" /></a>
<div class="result-name"><a href="{band_url}">{bandName}</a><br />
<span class="result-location">{location}</span>
</div><br />
</div>
</script>
<script type="text/template" id="template-fans-sidebar-search-result">
<div user-id="{userId}" class="sidebar-search-result">
<a class="avatar-small"><img src="{avatar_url}" /></a>
<div class="result-name"><a href="{profile_url}">{userName}</a><br />
<span class="result-location">{location}</span>
</div><br />
<br clear="all" />
</div>
<div user-id="{userId}" class="sidebar-search-result">
<a class="avatar-small"><img src="{avatar_url}" /></a>
<div class="result-name"><a href="{profile_url}">{userName}</a><br />
<span class="result-location">{location}</span>
</div><br />
<br clear="all" />
</div>
</script>
<!-- Friend panel template -->
<script type="text/template" id="template-friend-panel">
<li class="{cssClass}">
<div class="avatar-small"><img src="{avatar_url}" /></div>
<div class="friend-name">
{userName}<br/>
<span class="friend-status">
{status} {extra_info}
</span>
</div>
<div class="friend-icon">{info_image_url}</div>
<br clear="all" />
</li>
<li class="{cssClass}">
<div class="avatar-small"><img src="{avatar_url}" /></div>
<div class="friend-name">
{userName}<br/>
<span class="friend-status">
{status} {extra_info}
</span>
</div>
<div class="friend-icon">{info_image_url}</div>
<br clear="all" />
</li>
</script>
<!-- Friend request confirmation template -->
<script type="text/template" id="template-sidebar-invitation-sent">
<div user-id="{userId}" class="sidebar-search-connected">
<div style="margin-top:10px;">
<br />
<img src="/assets/content/icon_goodquality.png" width="16" height="16" />
<br />
<b>Invitation Sent!</b><br />
<a href="{profile_url}">View {first_name}&#39;s Profile</a>
</div>
<div user-id="{userId}" class="sidebar-search-connected">
<div style="margin-top:10px;">
<br />
<img src="/assets/content/icon_goodquality.png" width="16" height="16" />
<br />
<b>Invitation Sent!</b><br />
<a href="{profile_url}">View {first_name}&#39;s Profile</a>
</div>
</div>
</script>
<!-- Notification panel template -->
<script type="text/template" id="template-notification-panel">
<li notification-id="{notificationId}">
<div class="avatar-small"><img src="{avatar_url}" /></div>
<div class="note-text">
{text}
<em>({date})</em>
<div class="note-delete">
<a>
<img id="img-delete-notification" notification-id="{notificationId}" src="/assets/shared/icon_delete_sm.png" width="13" height="13" />
</a>
</div>
</div><br />
<div id="div-actions">
<a id="btn-notification-action" class="button-orange smallbutton right"></a>
</div>
<br/ >
<br clear="all" />
</li>
<li notification-id="{notificationId}">
<div class="avatar-small"><img src="{avatar_url}" /></div>
<div class="note-text">
{text}
<em>({date})</em>
<div class="note-delete">
<a>
<img id="img-delete-notification" notification-id="{notificationId}" src="/assets/shared/icon_delete_sm.png" width="13" height="13" />
</a>
</div>
</div><br />
<div id="div-actions">
<a id="btn-notification-action" class="button-orange smallbutton right"></a>
</div>
<br/ >
<br clear="all" />
</li>
</script>
<!-- Chat panel template -->
<script type="text/template" id="template-chat-panel">
<li>
<div class="avatar-small"><img src="{avatar_url}" /></div>
<div class="chat-text">
<strong>{label}:</strong>&nbsp;{text}
<em>({date})</em>
</div>
<br clear="all" />
</li>
<li>
<div class="avatar-small"><img src="{avatar_url}" /></div>
<div class="chat-text">
<strong>{label}:</strong>&nbsp;{text}
<em>({date})</em>
</div>
<br clear="all" />
</li>
</script>

View File

@ -45,6 +45,7 @@
<%= render "clients/banners/disconnected" %>
<%= render "overlay_small" %>
<%= render "help" %>
<div id="fb-root"></div>
<script type="text/javascript">
$(function() {
@ -95,7 +96,7 @@
var recordingManager = new JK.RecordingManager();
var invitationDialog = new JK.InvitationDialog(JK.app);
invitationDialog.initialize();
invitationDialog.initialize('<%= SampleApp::Application.config.facebook_key %>');
var shareDialog = new JK.ShareDialog(JK.app);
shareDialog.initialize();

View File

@ -31,6 +31,7 @@
<%= csrf_meta_tags %>
</head>
<body>
<%= javascript_include_tag "application" %>
<%= render 'layouts/header' %>
<div class="container">
<% flash.each do |key, value| %>
@ -38,6 +39,19 @@
<% end %>
</div>
<%= yield %>
<script type="text/javascript">
$(function () {
JK = JK || {};
<% if current_user %>
JK.currentUserId = '<%= current_user.id %>';
<% else %>
JK.currentUserId = null;
<% end %>
})
</script>
<%= render "shared/ga" %>
</body>
</html>

View File

@ -55,6 +55,18 @@
</div>
</div>
<script type="text/javascript">
$(function () {
JK = JK || {};
<% if current_user %>
JK.currentUserId = '<%= current_user.id %>';
<% else %>
JK.currentUserId = null;
<% end %>
})
</script>
<%= render "shared/ga" %>
</body>
</html>

View File

@ -37,6 +37,18 @@
<%= render "clients/footer" %>
</div>
<script type="text/javascript">
$(function () {
JK = JK || {};
<% if current_user %>
JK.currentUserId = '<%= current_user.id %>';
<% else %>
JK.currentUserId = null;
<% end %>
})
</script>
<%= render "shared/ga" %>
<!-- version info: <%= version %> -->
</body>

View File

@ -12,39 +12,48 @@
<%= @music_session.band.name %>
<% else %>
<div class="landing-avatar">
<% unless @music_session.creator.photo_url.blank? %>
<%= image_tag "#{@music_session.creator.photo_url}", {:alt => ""} %>
<% unless @music_session.user.photo_url.blank? %>
<%= image_tag "#{@music_session.user.photo_url}", {:alt => ""} %>
<% else %>
<%= image_tag "shared/avatar_generic.png", {:alt => ""} %>
<% end %>
</div>
<%= @music_session.creator.name %>
<%= @music_session.user.name %>
<% end %>
</div>
<div class="landing-details">
<div class="left f20 teal"><strong>SESSION: Live Session in Progress</strong></div>
<div class="right f14 grey"><%= @cmusic_session.created_at.strftime("%b %e %Y, %l:%M %p") %></div>
<div class="left f20 teal"><strong>SESSION</strong></div>
<div class="right f14 grey"><%= @music_session.created_at.strftime("%b %e %Y, %l:%M %p") %></div>
<br clear="all" /><br />
<h2 class="left"><%= @music_session.name %></h2>
<div class="right">
<a href="#"><%= image_tag "content/icon_like.png", {:width => 12, :height => 12} %>&nbsp;LIKE</a>&nbsp;&nbsp;&nbsp;
<a href="#"><%= image_tag "content/icon_share.png", {:width => 13, :height => 15} %>&nbsp;SHARE</a>
</div>
<br clear="all" />TODO: Which field is this in the database??<br /><br />
<div class="left w70"><%= @music_session.description %><br /><br /></div>
<% if @music_session.session_removed_at.blank? %>
<div class="right">
<a id="btnLike"><%= image_tag "content/icon_like.png", {:width => 12, :height => 12, :alt => ""} %>&nbsp;LIKE</a>&nbsp;&nbsp;&nbsp;
<a id="btnShare"><%= image_tag "content/icon_share.png", {:width => 13, :height => 15, :alt => ""} %>&nbsp;SHARE</a>
</div>
<% end %>
<br clear="all" />
<div class="w100">
<div class="recording-controls">
<a class="left mr20" href="#"><%= image_tag "content/icon_playbutton.png", {:width => 20, :height => 20} %></a>
<div class="session-status">LIVE SESSION IN PROGRESS</div>
<div class="recording-current">1:23</div>
<a class="left mr20" href="#"><%= image_tag "content/icon_playbutton.png", {:width => 20, :height => 20, :alt => ""} %></a>
<% if @music_session.session_removed_at.blank? %>
<div class="session-status">SESSION IN PROGRESS</div>
<div class="recording-current">1:23</div>
<% else %>
<div class="session-status-ended">LIVE SESSION ENDED</div>
<% end %>
</div>
<div class="left white"><%= @music_session.genres.split('|').first.id.capitalize %></div>
<div class="left white"><%= @music_session.genres.split('|').first.capitalize %></div>
<div class="right white">
<%= @music_session.comment_count %>
<%= image_tag "content/icon_comment.png", {:width => 13, :height => 12, :align => "absmiddle"} %>&nbsp;&nbsp;&nbsp;&nbsp;
<%= @music_session.like_count %>
<%= image_tag "content/icon_like.png", {:width => 12, :height => 12, :align => "absmiddle"} %>
<span id="spnCommentCount"><%= @music_session.comment_count %></span>
<%= image_tag "content/icon_comment.png", {:width => 13, :height => 12, :align => "absmiddle", :style => "vertical-align:middle", :alt => ""} %>&nbsp;&nbsp;&nbsp;&nbsp;
<span id="spnLikeCount"><%= @music_session.like_count %></span>
<%= image_tag "content/icon_like.png", {:width => 12, :height => 12, :align => "absmiddle", :style => "vertical-align:middle", :alt => ""} %>
</div>
</div>
<br clear="all" /><br />
@ -64,9 +73,69 @@
<% content_for :after_black_bar do %>
<br />
<%= render :partial => "shared/comments", :locals => {:comments => @music_session.comments} %>
<%= render :partial => "shared/comments", :locals => {:comments => @music_session.comments, :id => "txtSessionComment"} %>
<% end %>
<%= javascript_include_tag "web/sessions" %>
<%= render "clients/shareDialog" %>
<%= render :partial => "clients/shareDialog", :locals => {:session => @music_session, :share_token => @music_session.share_token} %>
<script type="text/javascript">
$(function () {
JK = JK || {};
<% if current_user %>
JK.currentUserId = '<%= current_user.id %>';
<% else %>
JK.currentUserId = null;
<% end %>
if (JK.currentUserId) {
JK.app = JK.JamKazam();
JK.app.initialize({inClient: false, layoutOpts: {layoutFooter: false}});
var shareDialog = new JK.ShareDialog(JK.app);
shareDialog.initialize();
$("#btnShare").click(function(e) {
shareDialog.showDialog();
});
$("#txtSessionComment").keypress(function(e) {
if (e.which === 13) {
addComment();
}
});
}
else {
$("#txtSessionComment").attr("disabled", "disabled");
$("#txtSessionComment").val("You must be logged in to add a comment.");
}
JK.sessionId = "<%= @music_session.music_session_id %>";
var rest = new JK.Rest();
$("#btnLike").click(like);
function like() {
rest.addSessionLike(JK.sessionId, JK.currentUserId)
.done(function(response) {
$("#spnLikeCount").html(parseInt($("#spnLikeCount").text()) + 1);
$("#btnLike").unbind("click");
});
}
function addComment() {
var comment = $("#txtSessionComment").val();
if ($.trim(comment).length > 0) {
rest.addSessionComment(JK.sessionId, JK.currentUserId, comment)
.done(function(response) {
$("#spnCommentCount").html(parseInt($("#spnCommentCount").text()) + 1);
$(".landing-comment-scroller").prepend(comment);
});
}
}
})
</script>

View File

@ -28,17 +28,17 @@
<br clear="all" /><br />
<h2 class="left"><%= @claimed_recording.name %></h2>
<div class="right">
<a href="#"><%= image_tag "content/icon_like.png", {:width => 12, :height => 12} %>&nbsp;LIKE</a>&nbsp;&nbsp;&nbsp;
<a href="#"><%= image_tag "content/icon_share.png", {:width => 13, :height => 15} %>&nbsp;SHARE</a>
<a id="btnLike"><%= image_tag "content/icon_like.png", {:width => 12, :height => 12, :alt => ""} %>&nbsp;LIKE</a>&nbsp;&nbsp;&nbsp;
<a id="btnShare"><%= image_tag "content/icon_share.png", {:width => 13, :height => 15, :alt => ""} %>&nbsp;SHARE</a>
</div>
<br clear="all" />TODO: Which field is this in the database??<br /><br />
<br clear="all" /><%= @claimed_recording.description %><br /><br />
<div class="w100">
<div class="recording-controls">
<a class="left" href="#"><%= image_tag "content/icon_playbutton.png", {:width => 20, :height => 20} %></a>
<a id="btnPlay" class="left"><%= image_tag "content/icon_playbutton.png", {:width => 20, :height => 20, :alt => ""} %></a>
<div class="recording-position">
<div class="recording-time">0:00</div>
<div class="recording-playback">
<div class="recording-slider"><%= image_tag "content/slider_playcontrols.png", {:width => 5, :height => 16} %></div>
<div class="recording-slider"><%= image_tag "content/slider_playcontrols.png", {:width => 5, :height => 16, :alt => ""} %></div>
</div>
<div class="recording-time">4:59</div>
</div>
@ -47,12 +47,12 @@
<div class="left white"><%= @claimed_recording.genre_id.capitalize %></div>
<div class="right white">
<%= @claimed_recording.recording.play_count %>
<%= image_tag "content/icon_arrow.png", {:width => 7, :height => 12, :align => "absmiddle"} %>&nbsp;&nbsp;&nbsp;&nbsp;
<%= @claimed_recording.recording.comment_count %>
<%= image_tag "content/icon_comment.png", {:width => 13, :height => 12, :align => "absmiddle"} %>&nbsp;&nbsp;&nbsp;&nbsp;
<%= @claimed_recording.recording.like_count %>
<%= image_tag "content/icon_like.png", {:width => 12, :height => 12, :align => "absmiddle"} %>
<span id="spnPlayCount"><%= @claimed_recording.recording.play_count %></span>
<%= image_tag "content/icon_arrow.png", {:width => 7, :height => 12, :align => "absmiddle", :alt => ""} %>&nbsp;&nbsp;&nbsp;&nbsp;
<span id="spnCommentCount"><%= @claimed_recording.recording.comment_count %></span>
<%= image_tag "content/icon_comment.png", {:width => 13, :height => 12, :align => "absmiddle", :alt => ""} %>&nbsp;&nbsp;&nbsp;&nbsp;
<span id="spnLikeCount"><%= @claimed_recording.recording.like_count %></span>
<%= image_tag "content/icon_like.png", {:width => 12, :height => 12, :align => "absmiddle", :alt => ""} %>
</div>
</div>
<br clear="all" /><br />
@ -72,9 +72,77 @@
<% content_for :after_black_bar do %>
<br />
<%= render :partial => "shared/comments", :locals => {:comments => @claimed_recording.recording.comments} %>
<%= render :partial => "shared/comments", :locals => {:comments => @claimed_recording.recording.comments, :id => "txtRecordingComment"} %>
<% end %>
<%= javascript_include_tag "web/recordings" %>
<%= render "clients/shareDialog" %>
<%= render :partial => "clients/shareDialog", :locals => {:recording => @claimed_recording, :share_token => @claimed_recording.share_token} %>
<script type="text/javascript">
$(function () {
JK = JK || {};
<% if current_user %>
JK.currentUserId = '<%= current_user.id %>';
<% else %>
JK.currentUserId = null;
<% end %>
if (JK.currentUserId) {
JK.app = JK.JamKazam();
JK.app.initialize({inClient: false, layoutOpts: {layoutFooter: false}});
var shareDialog = new JK.ShareDialog(JK.app);
shareDialog.initialize();
$("#btnShare").click(function(e) {
shareDialog.showDialog();
});
$("#txtRecordingComment").keypress(function(e) {
if (e.which === 13) {
addComment();
}
});
}
else {
$("#txtRecordingComment").attr("disabled", "disabled");
$("#txtRecordingComment").val("You must be logged in to add a comment.");
}
JK.recordingId = "<%= @claimed_recording.recording.id %>";
var rest = new JK.Rest();
$("#btnLike").click(like);
$("#btnPlay").click(play);
function like() {
rest.addRecordingLike(JK.recordingId, JK.currentUserId)
.done(function(response) {
$("#spnLikeCount").html(parseInt($("#spnLikeCount").text()) + 1);
$("#btnLike").unbind("click");
});
}
function play() {
rest.addRecordingPlay(JK.recordingId, JK.currentUserId)
.done(function(response) {
$("#spnPlayCount").html(parseInt($("#spnPlayCount").text()) + 1);
});
}
function addComment() {
var comment = $("#txtRecordingComment").val();
if ($.trim(comment).length > 0) {
rest.addRecordingComment(JK.recordingId, JK.currentUserId, comment)
.done(function(response) {
$("#spnCommentCount").html(parseInt($("#spnCommentCount").text()) + 1);
$(".landing-comment-scroller").prepend(comment);
});
}
}
})
</script>

View File

@ -4,7 +4,7 @@
<%= image_tag "shared/avatar_generic.png", {:alt => ""} %>
</div>
<div class="left w80 p10">
<textarea class="w100 p5 f15" rows="2" onfocus="$(this).html('')" onblur="if($(this).html() == ''){$(this).html('Enter a comment...')}">Enter a comment...</textarea>
<textarea id="<%= id %>" class="w100 p5 f15" rows="2" onfocus="$(this).html('')" onblur="if($(this).html() == ''){$(this).html('Enter a comment...')}">Enter a comment...</textarea>
</div>
<br clear="all" />

View File

@ -25,6 +25,7 @@
dimension1: '<%= ga_user_level %>',
dimension2: '<%= ga_user_type %>'
});
})(window);
</script>
<% else %>

View File

@ -1,26 +1,43 @@
<div class="landing-sidebar"><br />
<h2>More by <%= user.name %>:</h2><br />
<div class="grey f16"><em>Now:</em></div>
<div class="f16"><span class="teal"><strong>SESSION:</strong></span> <a href="#" class="white">Live Session in Progress</a></div>
<div class="f13 lightgrey">Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi commodo, ipsum sed pharetra gravida, orci magna rhoncus neque, id pulvinar odio lorem non turpis.</div>
<br /><br />
<div class="grey f16"><em>Yesterday:</em></div>
<div class="f16"><span class="orange"><strong>RECORDING:</strong></span> <a href="#" class="white">You Hurt Me Bad</a></div>
<div class="f13 lightgrey">Nullam sit amet enim. Suspendisse id velit vitae ligula volutpat condimentum. Aliquam erat volutpat. Sed quis velit. Nulla facilisi. </div>
<br /><br />
<div class="grey f16"><em>Dec. 18th:</em></div>
<div class="f16"><span class="teal"><strong>SESSION:</strong></span> <span class="grey">Session Ended. Unavailable.</span></div>
<div class="f13 lightgrey">Nulla libero. Vivamus pharetra posuere sapien. Nam consectetuer. Sed aliquam, nunc eget euismod ullamcorper, lectus nunc ullamcorper orci, fermentum bibendum enim nibh eget ipsum. </div>
<br /><br />
<div class="grey f16"><em>Dec. 12th:</em></div>
<div class="f16"><span class="orange"><strong>RECORDING:</strong></span> <a href="#" class="white">Bustin' My Chops</a></div>
<div class="f13 lightgrey">Donec porttitor ligula eu dolor. Maecenas vitae nulla consequat libero cursus venenatis. Nam magna enim, accumsan eu, blandit sed, blandit a, eros.</div>
<br /><br />
<div class="grey f16"><em>Dec. 10th:</em></div>
<div class="f16"><span class="teal"><strong>SESSION:</strong></span> <span class="grey">Session Ended. Unavailable.</span></div>
<div class="f13 lightgrey">Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi commodo, ipsum sed pharetra gravida, orci magna rhoncus neque, id pulvinar odio lorem non turpis. Nullam sit amet enim.</div>
<br /><br />
<div class="grey f16"><em>Nov. 29th:</em></div>
<div class="f16"><span class="teal"><strong>SESSION:</strong></span> <span class="grey">Session Ended. Unavailable.</span></div>
<div class="f13 lightgrey">Nulla libero. Vivamus pharetra posuere sapien. Nam consectetuer. </div>
<div class="landing-sidebar" style="z-index:900;"><br />
<h2>More by <%= user.name %>:</h2><br />
<% recent_history.each do |history_record| %>
<% if history_record.instance_of? ClaimedRecording %>
<div class="grey f16">
<em><%= history_record.created_at.strftime("%b #{history_record.created_at.day.ordinalize}") %>:</em>
</div>
<div class="f16">
<span class="orange"><strong>RECORDING:</strong></span>
<a href="/recordings/<%= history_record.recording_id %>" class="white">Test</a>
</div>
<% elsif history_record.instance_of? MusicSessionHistory %>
<div class="grey f16">
<em>
<% if history_record.session_removed_at.blank? %>
Now:
<% else %>
<%= history_record.session_removed_at.strftime("%b #{history_record.session_removed_at.day.ordinalize}") %>:
<% end %>
</em>
</div>
<div class="f16">
<span class="teal"><strong>SESSION:</strong></span>&nbsp;
<% if history_record.session_removed_at.blank? %>
<a href="/sessions/<%= history_record.music_session_id %>" class="white">Live Session in Progress</a>
<% else %>
<span class="grey">Session Ended. Unavailable.</span>
<% end %>
</div>
<% end %>
<div class="f13 lightgrey"><%= history_record.description %></div>
<% if history_record != recent_history.last %>
<br /><br />
<% end %>
<% end %>
</div>

View File

@ -9,14 +9,14 @@
<tr>
<td>
<div class="avatar-small m0">
<% unless track.user.photo_url.blank? %>
<%= image_tag "#{track.user.photo_url}", {:alt => ""} %>
<% unless track.musician.photo_url.blank? %>
<%= image_tag "#{track.musician.photo_url}", {:alt => ""} %>
<% else %>
<%= image_tag "shared/avatar_generic.png", {:alt => ""} %>
<% end %>
</div>
</td>
<td style="width:150px;"><div class="lightgrey f15 ml10"><%= track.user.name %></div></td>
<td style="width:150px;"><div class="lightgrey f15 ml10"><%= track.musician.name %></div></td>
<td class="p10">
<div class="ml10">
<%= image_tag "content/icon_instrument_#{track.instrument_id.tr(" ", "_")}45.png", {:width => 32, :alt => "", :title => "#{track.instrument_id}"} %>

View File

@ -27,6 +27,7 @@
<ul class="shortcuts-submenu">
<li class="google-invite"><%= link_to "Google", '#' %></li>
<li class="email-invite"><%= link_to "Email", '#' %></li>
<li class="facebook-invite"><%= link_to "Facebook", '#' %></li>
</ul>
</li>
<li class="download-app"><%= link_to "Download App", downloads_path, :rel => "external" %></li>

View File

@ -164,8 +164,12 @@ include JamRuby
config.bugsnag_notify_release_stages = ["production"] # add 'development' if you want to test a bugsnag feature locally
config.ga_ua = 'UA-44184562-2' # google analytics
config.ga_endpoint = 'www.google-analytics.com'
config.ga_ua_version = '1'
config.ga_anonymous_client_id = '555'
config.ga_suppress_admin = true
config.redis_host = "localhost:6379"
config.audiomixer_path = "/var/lib/audiomixer/audiomixer/audiomixerapp"

View File

@ -11,7 +11,6 @@ development:
host: localhost
pool: 5
timeout: 5000
prepared_statements: false
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
@ -24,7 +23,6 @@ test: &test
host: localhost
pool: 5
timeout: 5000
prepared_statements: false
production:
adapter: postgresql
@ -34,7 +32,6 @@ production:
host: localhost
pool: 5
timeout: 5000
prepared_statements: false
cucumber:
<<: *test

View File

@ -1 +1,13 @@
Resque.redis = Rails.application.config.redis_host
Resque.redis = Rails.application.config.redis_host
if !$rails_rake_task && Rails.env == 'development' && ENV['RUN_JOBS_INLINE'] == '1'
Thread.new do
system('INTERVAL=1 bundle exec rake all_jobs')
end
Thread.new do
system('bundle exec rake scheduler')
end
end

View File

@ -71,6 +71,14 @@ SampleApp::Application.routes.draw do
# email update
match '/confirm_email' => 'users#finalize_update_email', :as => 'confirm_email' # NOTE: if you change this, you break outstanding email changes because links in user inboxes are broken
# embed resque-web if this is development mode
if Rails.env == "development"
require 'resque/server'
require 'resque-retry'
require 'resque-retry/server'
mount Resque::Server.new, :at => "/resque" if Rails.env == "development"
end
scope '/corp' do
match '/about', to: 'corps#about', as: 'corp_about'
match '/contact', to: 'corps#contact', as: 'corp_contact'
@ -95,6 +103,7 @@ SampleApp::Application.routes.draw do
match '/sessions/:id/perf' => 'api_music_sessions#perf_upload', :via => :put
match '/sessions/:id/comments' => 'api_music_sessions#add_comment', :via => :post
match '/sessions/:id/likes' => 'api_music_sessions#add_like', :via => :post
match '/sessions/:id/history' => 'api_music_sessions#history_show', :via => :get
# music session tracks
match '/sessions/:id/tracks' => 'api_music_sessions#track_create', :via => :post
@ -324,4 +333,5 @@ SampleApp::Application.routes.draw do
match '/icecast/listener_add' => 'api_icecast#listener_add', :via => :post
match '/icecast/listener_remove' => 'api_icecast#listener_remove', :via => :post
end
end

View File

@ -32,7 +32,7 @@ listen 3100, :tcp_nopush => true
timeout 30
# feel free to point this anywhere accessible on the filesystem
pid "/var/run/jam-web.pid"
pid "/var/run/jam-web/jam-web.pid"
# By default, the Unicorn logger will write to stderr.
# Additionally, ome applications/frameworks log to stderr or stdout,

View File

@ -7,24 +7,6 @@ echo "starting build..."
if [ "$?" = "0" ]; then
echo "build succeeded"
if [ ! -z "$PACKAGE" ]; then
if [[ "$GIT_BRANCH" == *develop* || "$GIT_BRANCH" == *master* ]]; then
echo "publishing ubuntu package (.deb)"
DEBPATH=`find target/deb -name *.deb`
DEBNAME=`basename $DEBPATH`
curl -f -T $DEBPATH $DEB_SERVER/$DEBNAME
if [ "$?" != "0" ]; then
echo "deb publish failed"
exit 1
fi
echo "done publishing deb"
else
echo "Skipping publish since branch is neither master or develop..."
fi
fi
else
echo "build failed"
exit 1

View File

@ -3,5 +3,7 @@ description "jam-web"
start on startup
start on runlevel [2345]
stop on runlevel [016]
setuid jam-web
setgid jam-web
exec start-stop-daemon --start --chdir /var/lib/jam-web --exec /var/lib/jam-web/script/package/upstart-run.sh

View File

@ -14,18 +14,16 @@ mkdir -p /var/lib/$NAME/log
mkdir -p /var/lib/$NAME/tmp
mkdir -p /etc/$NAME
mkdir -p /var/log/$NAME
mkdir -p /var/run/$NAME
chown -R $USER:$GROUP /var/lib/$NAME
chown -R $USER:$GROUP /etc/$NAME
chown -R $USER:$GROUP /var/log/$NAME
chown -R $USER:$GROUP /var/run/$NAME
# make log folders for jobs
mkdir -p /var/log/audiomixer-worker
mkdir -p /var/log/icecast-worker
mkdir -p /var/log/any-job-worker
mkdir -p /var/log/scheduler-worker
chown -R $USER:$GROUP /var/log/audiomixer-worker
chown -R $USER:$GROUP /var/log/icecast-worker
chown -R $USER:$GROUP /var/log/any-job-worker
chown -R $USER:$GROUP /var/log/scheduler-worker

View File

@ -71,6 +71,7 @@ describe "Invited Users API ", :type => :api do
end
it "cant create with no email" do
pending "changes to invitations broke this"
post '/api/invited_users.json', {:note => "please join"}.to_json, "CONTENT_TYPE" => 'application/json'
last_response.status.should eql(422)
body = JSON.parse(last_response.body)
@ -79,6 +80,7 @@ describe "Invited Users API ", :type => :api do
end
it "cant create with blank email" do
pending "changes to invitations broke this"
post '/api/invited_users.json', {:email => "", :note => "please join"}.to_json, "CONTENT_TYPE" => 'application/json'
last_response.status.should eql(422)
body = JSON.parse(last_response.body)

View File

@ -15,6 +15,7 @@ else
gem 'jam_db', "0.1.#{ENV["BUILD_NUMBER"]}"
gem 'jampb', "0.1.#{ENV["BUILD_NUMBER"]}"
gem 'jam_ruby', "0.1.#{ENV["BUILD_NUMBER"]}"
ENV['NOKOGIRI_USE_SYSTEM_LIBRARIES'] ||= "true"
end

View File

@ -9,7 +9,6 @@ echo "starting build..."
if [ "$?" = "0" ]; then
echo "build succeeded"
if [[ "$GIT_BRANCH" == *develop* || "$GIT_BRANCH" == *master* ]]; then
# generate gem version based on jenkins build number
if [ -z $BUILD_NUMBER ]; then
@ -35,24 +34,6 @@ EOF
exit 1
fi
echo "done publishing gem"
if [ ! -z "$PACKAGE" ]; then
echo "publishing ubuntu package (.deb)"
DEBPATH=`find target/deb -name *.deb`
DEBNAME=`basename $DEBPATH`
curl -f -T $DEBPATH $DEB_SERVER/$DEBNAME
if [ "$?" != "0" ]; then
echo "deb publish failed"
exit 1
fi
echo "done publishing deb"
fi
else
echo "Skipping publish since branch is neither master or develop..."
fi
else
echo "build failed"
exit 1

View File

@ -11,5 +11,7 @@ GROUP="$NAME"
cp /var/lib/$NAME/script/package/$NAME.conf /etc/init/$NAME.conf
mkdir -p /var/lib/$NAME/log
mkdir -p /var/run/$NAME
chown -R $USER:$GROUP /var/lib/$NAME
chown -R $USER:$GROUP /var/run/$NAME

View File

@ -3,5 +3,7 @@ description "websocket-gateway"
start on startup
start on runlevel [2345]
stop on runlevel [016]
setuid websocket-gateway
setgid websocket-gateway
exec start-stop-daemon --start --chdir /var/lib/websocket-gateway --exec /var/lib/websocket-gateway/script/package/upstart-run.sh