diff --git a/admin/Gemfile b/admin/Gemfile index 756225a1a..1a1a7c248 100644 --- a/admin/Gemfile +++ b/admin/Gemfile @@ -44,7 +44,7 @@ gem 'rails3-jquery-autocomplete' gem 'activeadmin' #, github: 'activeadmin', branch: '0-6-stable' gem 'mime-types', '1.25' gem 'meta_search' -gem 'fog', "~> 1.32.0" +gem 'fog' gem 'unf', '0.1.3' #optional fog dependency gem 'country-select' gem 'aasm', '3.0.16' diff --git a/admin/app/admin/download_tracker.rb b/admin/app/admin/download_tracker.rb new file mode 100644 index 000000000..2577736e5 --- /dev/null +++ b/admin/app/admin/download_tracker.rb @@ -0,0 +1,34 @@ +ActiveAdmin.register JamRuby::DownloadTracker, :as => 'DownloadTrackers' do + + menu :label => 'Download Trackers', :parent => 'JamTracks' + + config.batch_actions = false + config.filters = true + config.per_page = 50 + + filter :remote_ip + + index do + column 'User' do |oo| oo.user ? link_to(oo.user.email, oo.user.admin_url, {:title => oo.user.email}) : '' end + column 'Remote IP' do |oo| oo.remote_ip end + column 'JamTrack' do |oo| oo.jam_track end + column 'Paid' do |oo| oo.paid end + column 'Blacklisted?' do |oo| IpBlacklist.listed(oo.remote_ip) ? 'Yes' : 'No' end + column "" do |oo| + link_to 'Blacklist This IP', "download_trackers/#{oo.id}/blacklist_by_ip" + end + end + + member_action :blacklist_by_ip, :method => :get do + tracker = DownloadTracker.find(params[:id]) + + if !IpBlacklist.listed(tracker.remote_ip) + ip = IpBlacklist.new + ip.remote_ip = tracker.remote_ip + ip.save! + end + + redirect_to admin_download_trackers_path, :notice => "IP address #{tracker.remote_ip} blacklisted." + end + +end diff --git a/admin/app/admin/fake_purchaser.rb b/admin/app/admin/fake_purchaser.rb index b8c5caa31..87d167330 100644 --- a/admin/app/admin/fake_purchaser.rb +++ b/admin/app/admin/fake_purchaser.rb @@ -32,6 +32,7 @@ ActiveAdmin.register_page "Fake Purchaser" do jam_track_right.user = user jam_track_right.jam_track = jam_track jam_track_right.is_test_purchase = true + jam_track_right.version = jam_track.version jam_track_right.save! count = count + 1 end diff --git a/admin/app/admin/gift_card_upload.rb b/admin/app/admin/gift_card_upload.rb new file mode 100644 index 000000000..87e69850e --- /dev/null +++ b/admin/app/admin/gift_card_upload.rb @@ -0,0 +1,41 @@ +ActiveAdmin.register_page "Giftcarduploads" do + + menu :label => 'Gift Cards Upload', :parent => 'JamTracks' + + page_action :upload_giftcards, :method => :post do + GiftCard.transaction do + + puts params + + file = params[:jam_ruby_gift_card][:csv] + array_of_arrays = CSV.read(file.tempfile.path) + array_of_arrays.each do |row| + if row.length != 1 + raise "UKNONWN CSV FORMAT! Must be 1 column" + end + + code = row[0] + + gift_card = GiftCard.new + gift_card.code = code + gift_card.card_type = params[:jam_ruby_gift_card][:card_type] + gift_card.origin = file .original_filename + gift_card.save! + end + + redirect_to admin_giftcarduploads_path, :notice => "Created #{array_of_arrays.length} gift cards!" + end + end + + content do + semantic_form_for GiftCard.new, :url => admin_giftcarduploads_upload_giftcards_path, :builder => ActiveAdmin::FormBuilder do |f| + f.inputs "Upload Gift Cards" do + f.input :csv, as: :file, required: true, :label => "A single column CSV that contains ONE type of gift card (5 JamTrack, 10 JamTrack, etc)" + f.input :card_type, required:true, as: :select, :collection => JamRuby::GiftCard::CARD_TYPES + end + f.actions + end + end + +end + diff --git a/admin/app/admin/gift_cards.rb b/admin/app/admin/gift_cards.rb new file mode 100644 index 000000000..8e1d4e80d --- /dev/null +++ b/admin/app/admin/gift_cards.rb @@ -0,0 +1,24 @@ +ActiveAdmin.register JamRuby::GiftCard, :as => 'GiftCards' do + + menu :label => 'Gift Cards', :parent => 'JamTracks' + + config.batch_actions = false + config.filters = true + config.per_page = 50 + + scope("Redeemed Most Recently", default: true) { |scope| scope.where('user_id IS NOT NULL').order('updated_at DESC') } + scope("Available") { |scope| scope.where('user_id is NULL') } + + filter :card_type + filter :origin + filter :code + + index do + column 'User' do |oo| oo.user ? link_to(oo.user.email, oo.user.admin_url, {:title => oo.user.email}) : '' end + column 'Code' do |oo| oo.code end + column 'Card Type' do |oo| oo.card_type end + column 'Origin' do |oo| oo.origin end + column 'Created' do |oo| oo.created_at end + end + +end diff --git a/admin/app/admin/jam_tracks.rb b/admin/app/admin/jam_tracks.rb index 534b65903..4a039e32f 100644 --- a/admin/app/admin/jam_tracks.rb +++ b/admin/app/admin/jam_tracks.rb @@ -11,7 +11,8 @@ ActiveAdmin.register JamRuby::JamTrack, :as => 'JamTracks' do scope("Default", default: true) { |scope| scope } scope("Onboarding TODO") { |scope| scope.where('onboarding_exceptions is not null') } scope("Tency Only") { |scope| scope.joins('INNER JOIN jam_track_licensors as licensors ON jam_tracks.licensor_id = licensors.id').where("licensors.name = 'Tency Music'") } - scope("Onboarding TODO w/ Tency Only") { |scope| scope.joins('INNER JOIN jam_track_licensors as licensors ON jam_tracks.licensor_id = licensors.id').where("licensors.name = 'Tency Music'").where('onboarding_exceptions is not null') } + scope("TimTracks Only") { |scope| scope.joins('INNER JOIN jam_track_licensors as licensors ON jam_tracks.licensor_id = licensors.id').where("licensors.name = 'Tim Waurick'") } + # scope("Onboarding TODO w/ Tency Only") { |scope| scope.joins('INNER JOIN jam_track_licensors as licensors ON jam_tracks.licensor_id = licensors.id').where("licensors.name = 'Tency Music'").where('onboarding_exceptions is not null') } form :partial => 'form' diff --git a/admin/app/controllers/email_controller.rb b/admin/app/controllers/email_controller.rb index 1e0c55182..4c999fd30 100644 --- a/admin/app/controllers/email_controller.rb +++ b/admin/app/controllers/email_controller.rb @@ -15,5 +15,11 @@ class EmailController < ApplicationController headers['Content-Type'] ||= 'text/csv' @users = User.where(subscribe_email: true) + + # if specified, return only users that have redeemed or bought a JamTrack + if params[:any_jam_track] + @users = @users.select('DISTINCT users.id, email, first_name, last_name').joins(:sales => :sale_line_items).where("sale_line_items.product_type = 'JamTrack'") + end + end end \ No newline at end of file diff --git a/admin/app/views/admin/jam_tracks/_form.html.slim b/admin/app/views/admin/jam_tracks/_form.html.slim index 334cdc27e..ac038e5ca 100644 --- a/admin/app/views/admin/jam_tracks/_form.html.slim +++ b/admin/app/views/admin/jam_tracks/_form.html.slim @@ -13,6 +13,7 @@ = f.input :publisher, :input_html => { :rows=>1, :maxlength=>1000 } = f.input :licensor, collection: JamRuby::JamTrackLicensor.all, include_blank: true = f.input :genres + = f.input :year = f.input :duration, hint: 'this should rarely need editing because it comes from the import process' = f.input :sales_region, collection: JamRuby::JamTrack::SALES_REGION, include_blank: false = f.input :price, :required => true, :input_html => {type: 'numeric'} diff --git a/admin/config/initializers/gift_cards.rb b/admin/config/initializers/gift_cards.rb new file mode 100644 index 000000000..8c967ccb4 --- /dev/null +++ b/admin/config/initializers/gift_cards.rb @@ -0,0 +1,9 @@ +class JamRuby::GiftCard + + attr_accessor :csv + + + def process_csv + + end +end diff --git a/admin/config/initializers/jam_tracks.rb b/admin/config/initializers/jam_tracks.rb index 33efd995c..60537c467 100644 --- a/admin/config/initializers/jam_tracks.rb +++ b/admin/config/initializers/jam_tracks.rb @@ -2,28 +2,5 @@ class JamRuby::JamTrack # add a custom validation - attr_accessor :preview_generate_error - before_save :jmep_json_generate - validate :jmep_text_validate - - def jmep_text_validate - begin - JmepManager.execute(self.jmep_text) - rescue ArgumentError => err - errors.add(:jmep_text, err.to_s) - end - end - - def jmep_json_generate - self.licensor_id = nil if self.licensor_id == '' - self.jmep_json = nil if self.jmep_json == '' - self.time_signature = nil if self.time_signature == '' - - begin - self[:jmep_json] = JmepManager.execute(self.jmep_text) - rescue ArgumentError => err - #errors.add(:jmep_text, err.to_s) - end - end end diff --git a/db/manifest b/db/manifest index 4da7a1b28..c75998c34 100755 --- a/db/manifest +++ b/db/manifest @@ -310,3 +310,8 @@ web_playable_jamtracks.sql affiliate_partner_rate.sql track_downloads.sql jam_track_lang_idx.sql +giftcard.sql +add_description_to_crash_dumps.sql +acappella.sql +purchasable_gift_cards.sql +versionable_jamtracks.sql \ No newline at end of file diff --git a/db/up/acappella.sql b/db/up/acappella.sql new file mode 100644 index 000000000..cf17e052d --- /dev/null +++ b/db/up/acappella.sql @@ -0,0 +1,2 @@ +INSERT INTO genres (id, description) values ('acapella', 'A Capella'); +ALTER TABLE jam_track_licensors ADD COLUMN slug VARCHAR UNIQUE; diff --git a/db/up/add_description_to_crash_dumps.sql b/db/up/add_description_to_crash_dumps.sql new file mode 100644 index 000000000..e08540d85 --- /dev/null +++ b/db/up/add_description_to_crash_dumps.sql @@ -0,0 +1 @@ +ALTER TABLE crash_dumps ADD COLUMN description VARCHAR(20000); \ No newline at end of file diff --git a/db/up/giftcard.sql b/db/up/giftcard.sql new file mode 100644 index 000000000..eae33c13e --- /dev/null +++ b/db/up/giftcard.sql @@ -0,0 +1,13 @@ +CREATE TABLE gift_cards ( + id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(), + code VARCHAR(64) UNIQUE NOT NULL, + user_id VARCHAR (64) REFERENCES users(id) ON DELETE CASCADE, + card_type VARCHAR(64) NOT NULL, + origin VARCHAR(200), + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX gift_card_user_id_idx ON gift_cards(user_id); + +ALTER TABLE users ADD COLUMN gifted_jamtracks INTEGER DEFAULT 0; diff --git a/db/up/purchasable_gift_cards.sql b/db/up/purchasable_gift_cards.sql new file mode 100644 index 000000000..9ef8d29fa --- /dev/null +++ b/db/up/purchasable_gift_cards.sql @@ -0,0 +1,24 @@ + + +CREATE TABLE gift_card_types ( + id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(), + card_type VARCHAR(64) NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +INSERT INTO gift_card_types (id, card_type) VALUES ('jam_tracks_5', 'jam_tracks_5'); +INSERT INTO gift_card_types (id, card_type) VALUES ('jam_tracks_10', 'jam_tracks_10'); + +CREATE TABLE gift_card_purchases ( + id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id VARCHAR(64) NOT NULL REFERENCES users(id) ON DELETE SET NULL, + gift_card_type_id VARCHAR(64) REFERENCES gift_card_types(id) ON DELETE SET NULL, + recurly_adjustment_uuid VARCHAR(500), + recurly_adjustment_credit_uuid VARCHAR(500), + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + + +ALTER TABLE sale_line_items ADD COLUMN gift_card_purchase_id VARCHAR(64) REFERENCES gift_card_purchases(id); diff --git a/db/up/versionable_jamtracks.sql b/db/up/versionable_jamtracks.sql new file mode 100644 index 000000000..9751bb940 --- /dev/null +++ b/db/up/versionable_jamtracks.sql @@ -0,0 +1 @@ +ALTER TABLE jam_track_rights ADD COLUMN version VARCHAR NOT NULL DEFAULT '0'; \ No newline at end of file diff --git a/ruby/lib/jam_ruby.rb b/ruby/lib/jam_ruby.rb index 21e2446d9..b3772ec14 100755 --- a/ruby/lib/jam_ruby.rb +++ b/ruby/lib/jam_ruby.rb @@ -253,6 +253,9 @@ require "jam_ruby/models/musician_search" require "jam_ruby/models/band_search" require "jam_ruby/import/tency_stem_mapping" require "jam_ruby/models/jam_track_search" +require "jam_ruby/models/gift_card" +require "jam_ruby/models/gift_card_purchase" +require "jam_ruby/models/gift_card_type" include Jampb diff --git a/ruby/lib/jam_ruby/constants/validation_messages.rb b/ruby/lib/jam_ruby/constants/validation_messages.rb index 662fce608..e84dff7c3 100644 --- a/ruby/lib/jam_ruby/constants/validation_messages.rb +++ b/ruby/lib/jam_ruby/constants/validation_messages.rb @@ -19,6 +19,8 @@ module ValidationMessages # sessions SESSION_NOT_FOUND = "Session not found." + NOT_FOUND = 'not found' + # genres RECORDING_GENRE_LIMIT_EXCEEDED = "No more than 1 genre is allowed." BAND_GENRE_LIMIT_EXCEEDED = "No more than 3 genres are allowed." diff --git a/ruby/lib/jam_ruby/jam_track_importer.rb b/ruby/lib/jam_ruby/jam_track_importer.rb index a92aca61b..fed33d413 100644 --- a/ruby/lib/jam_ruby/jam_track_importer.rb +++ b/ruby/lib/jam_ruby/jam_track_importer.rb @@ -29,10 +29,187 @@ module JamRuby end def finish(reason, detail) + @@log.info("JamTrackImporter:#{self.name} #{reason}") self.reason = reason self.detail = detail end + def import_click_track(jam_track) + # we need to download the click track, if it exists. + Dir.mktmpdir do |tmp_dir| + + @@log.info("importing clicking track for #{jam_track.original_artist}:#{jam_track.name}") + + if jam_track.click_track + @@log.info("already has click track: #{jam_track.original_artist}:#{jam_track.name}") + finish('success', 'already_has_click_track') + return + end + + click_track_file = jam_track.click_track_file + if click_track_file.nil? + @@log.info("no click track for #{jam_track.original_artist}:#{jam_track.name}") + finish('success', 'no_click_track') + return + end + + original_filename = click_track_file[:original_filename] + + if original_filename.nil? + @@log.info("no click track s3 path for #{jam_track.original_artist}:#{jam_track.name}") + finish('no_original_source', 'click track is missing s3 path:' + click_track_file.id) + return + end + + #wav_file = File.join(tmp_dir, File.basename(click_track_file[:original_filename])) + #JamTrackImporter.song_storage_manager.download(click_track_file[:original_filename], wav_file) + + JamTrack.transaction do + click_track = jam_track.click_track + + if click_track.nil? + click_track = JamTrackTrack.new + click_track.original_filename = click_track_file[:original_filename] + click_track.original_audio_s3_path = click_track_file[:original_filename] + click_track.track_type = 'Click' + click_track.part = 'Clicktrack' + click_track.instrument_id = 'computer' + click_track.jam_track = jam_track + click_track.position = 10000 + if !click_track.save + @@log.error("unable to create jamtrack click track #{click_track.errors.inspect}") + finish("jam_track_click", "unable to create: #{click_track.errors.inspect}") + return false + end + end + + jam_track.increment_version! + + # with the click track in hand, flesh out the details + synchronize_audio_track(jam_track, tmp_dir, false, click_track) + + finish('success', nil) + end + end + end + + def generate_jmep(jam_track) + if !jam_track.blank? + finish('success', 'jmep already exists') + return + else + # we need to download the click track, if it exists. + Dir.mktmpdir do |tmp_dir| + + master_track = jam_track.master_track + + click_track = jam_track.click_track_file + + if master_track.nil? + finish('no_master_track', nil) + return + end + + master_track_file = File.join(tmp_dir, File.basename(master_track[:url_48])) + begin + JamTrackImporter.private_s3_manager.download(master_track.url_by_sample_rate(44), master_track_file) + rescue Exception => e + @@log.error("unable to download master track") + finish("no-download-master", master_track.url_by_sample_rate(44)) + return + end + + if click_track + click_track_file = File.join(tmp_dir, File.basename(click_track[:original_filename])) + JamTrackImporter.song_storage_manager.download(click_track[:original_filename], click_track_file) + else + # we'll use the master for click analysis. not ideal, but would work + click_track_file = master_track_file + end + + start_time = determine_start_time(master_track_file, tmp_dir, master_track[:url]) + + # bpm comes from git clone http://www.pogo.org.uk/~mark/bpm-tools.git + + sox="sox #{Shellwords.escape(click_track_file)} -t raw -r 44100 -e float -c 1 - | bpm" + cmd = "bash -c #{Shellwords.escape(sox)}" + @@log.debug("executing cmd #{cmd}") + output=`#{cmd}` + + result_code = $?.to_i + + if result_code == 0 + bpm = output.to_f + + @@log.debug("bpm: #{bpm} start_time: #{start_time}") + + metro_fin = "#{Time.at(start_time).utc.strftime("%H:%M:%S")}:#{((start_time - start_time.to_i) * 1000).round}" + + jmep = "" + jmep << "# created via code using bpm/silence detection (bpm:#{bpm})\r\n" + jmep << "prelude@10.0 #number of seconds before music starts\r\n" + jmep << "metro_fin@#{metro_fin} bpm=#{bpm}, ticks=8, pmode=stream, name=Beep, play=mono" + + @@log.info("jmep generated: #{jmep}") + + jam_track.jmep_text = jmep + if jam_track.save + finish('success', nil) + else + @@log.error("jamtrack did not save. #{jam_track.errors.inspect}") + finish("no-save", "jamtrack did not save. #{jam_track.errors.inspect}") + return + end + else + finish("bpm-fail", "failed to run bpm: #{output}") + return + end + end + end + end + + + + def determine_start_time(audio_file, tmp_dir, original_filename) + burp_gaps = ['0.3', '0.2', '0.1', '0.05'] + + out_wav = File.join(tmp_dir, 'stripped.wav') + total_time_command = "soxi -D \"#{audio_file}\"" + total_time = `#{total_time_command}`.to_f + + result_code = -20 + stripped_time = total_time # default to the case where we just start the preview at the beginning + + burp_gaps.each do |gap| + command_strip_lead_silence = "sox \"#{audio_file}\" \"#{out_wav}\" silence 1 #{gap} 1%" + + @@log.debug("stripping silence: " + command_strip_lead_silence) + + output = `#{command_strip_lead_silence}` + + result_code = $?.to_i + + if result_code == 0 + stripped_time_command = "soxi -D \"#{out_wav}\"" + stripped_time_test = `#{stripped_time_command}`.to_f + + if stripped_time_test < 1 # meaning a very short duration + @@log.warn("could not determine the start of non-silence. assuming beginning") + stripped_time = total_time # default to the case where we just start the preview at the beginning + else + stripped_time = stripped_time_test # accept the measured time of the stripped file and move on by using break + break + end + else + @@log.warn("unable to determine silence for jam_track #{original_filename}, #{output}") + stripped_time = total_time # default to the case where we just start the preview at the beginning + end + end + + preview_start_time = total_time - stripped_time + + preview_start_time + end def synchronize_preview_dev(jam_track) jam_track.jam_track_tracks.each do |track| @@ -231,6 +408,7 @@ module JamRuby original_artist = parsed_metalocation[1] name = parsed_metalocation[2] + JamTrackImporter.summaries[:unique_artists] << original_artist success = dry_run_metadata(metadata, original_artist, name) @@ -269,6 +447,11 @@ module JamRuby @storage_format == 'Tency' end + def is_tim_tracks_storage? + assert_storage_set + @storage_format == 'TimTracks' + end + def assert_storage_set raise "no storage_format set" if @storage_format.nil? end @@ -276,7 +459,7 @@ module JamRuby def parse_metalocation(metalocation) # metalocation = mapped/4 Non Blondes - What's Up - 6475/meta.yml - if is_tency_storage? + if is_tency_storage? || is_tim_tracks_storage? suffix = '/meta.yml' @@ -305,15 +488,21 @@ module JamRuby return nil end - last_dash = metalocation.rindex('-') - if last_dash - song = metalocation[(first_dash+3)...last_dash].strip + if is_tim_tracks_storage? + song = metalocation[(first_dash+3)..-1].strip bits << song - else - finish("invalid_metalocation", "metalocation not valid #{metalocation}") - return nil + elsif is_tency_storage? + last_dash = metalocation.rindex('-') + if last_dash + song = metalocation[(first_dash+3)...last_dash].strip + bits << song + else + finish("invalid_metalocation", "metalocation not valid #{metalocation}") + return nil + end end + bits << 'meta.yml' bits else @@ -338,24 +527,14 @@ module JamRuby end end - # if you change this, it will (at least without some work )break development usage of jamtracks - def gen_plan_code(original_artist, name) - # remove all non-alphanumeric chars from artist as well as name - artist_code = original_artist.gsub(/[^0-9a-z]/i, '').downcase - name_code = name.gsub(/[^0-9a-z]/i, '').downcase - "jamtrack-#{artist_code[0...20]}-#{name_code}"[0...50] # make sure it's a max of 50 long - end - def dry_run_metadata(metadata, original_artist, name) self.name = metadata["name"] || name original_artist = metadata["original_artist"] || original_artist - plan_code = metadata["plan_code"] || gen_plan_code(original_artist, self.name) description = metadata["description"] @@log.debug("#{self.name} original_artist=#{original_artist}") - @@log.debug("#{self.name} plan_code=#{plan_code}") true end @@ -494,7 +673,6 @@ module JamRuby jam_track.year = metadata[:year] jam_track.genres = determine_genres(metadata) jam_track.language = determine_language(metadata) - jam_track.plan_code = metadata["plan_code"] || gen_plan_code(jam_track.original_artist, jam_track.name) jam_track.price = 1.99 jam_track.reproduction_royalty_amount = nil jam_track.reproduction_royalty = true @@ -507,16 +685,25 @@ module JamRuby jam_track.alternative_license_status = false jam_track.hfa_license_desired = true jam_track.server_fixation_date = Time.now - jam_track.slug = metadata['slug'] - unless jam_track.slug - jam_track.generate_slug - end - if is_tency_storage? jam_track.vendor_id = metadata[:id] - jam_track.licensor = JamTrackLicensor.find_by_name('Tency Music') + jam_track.licensor = JamTrackLicensor.find_by_name!('Tency Music') #add_licensor_metadata('Tency Music', metalocation) + elsif is_tim_tracks_storage? + jam_track.vendor_id = metadata[:id] + jam_track.licensor = JamTrackLicensor.find_by_name!('Tim Waurick') end + jam_track.slug = metadata['slug'] + if jam_track.slug.nil? + jam_track.generate_slug + end + jam_track.plan_code = metadata["plan_code"] + if jam_track.plan_code.nil? + jam_track.gen_plan_code + end + + + else if !options[:resync_audio] #@@log.debug("#{self.name} skipped because it already exists in database") @@ -664,8 +851,11 @@ module JamRuby instrument = 'other' part = 'Bouzouki' elsif potential_instrument == 'claps' || potential_instrument == 'hand claps' - instrument = 'computer' + instrument = 'other' part = 'Claps' + elsif potential_instrument == 'snaps' || potential_instrument == 'snap' + instrument = 'other' + part = 'Snaps' else found_instrument = Instrument.find_by_id(potential_instrument) if found_instrument @@ -774,7 +964,6 @@ module JamRuby end end end - end @@ -900,6 +1089,10 @@ module JamRuby if track.track_type == 'Master' instrument_weight = 1000 end + + if track.track_type == 'Click' + instrument_weight = 10000 + end end @@ -976,7 +1169,22 @@ module JamRuby end - sorted_tracks[sorted_tracks.length - 1].position = 1000 + # get click/master tracks position re-set correctly + + last_track = sorted_tracks[sorted_tracks.length - 1] + second_to_last = sorted_tracks[sorted_tracks.length - 2] + + if last_track.track_type == 'Master' + last_track.position = 1000 + elsif last_track.track_type == 'Click' + last_track.position = 10000 + end + + if second_to_last.track_type == 'Master' + second_to_last.position = 1000 + elsif second_to_last.track_type == 'Click' + second_to_last.position = 10000 + end sorted_tracks end @@ -1082,7 +1290,6 @@ module JamRuby @@log.debug("#{self.name} track! instrument: #{parsed_wav[:instrument] ? parsed_wav[:instrument] : 'N/A'}, part: #{parsed_wav[:part] ? parsed_wav[:part] : 'N/A'}, filename: #{parsed_wav[:filename]} ") end - track.instrument_id = parsed_wav[:instrument] || 'other' track.track_type = 'Track' track.part = parsed_wav[:part] || "Other #{unknowns}" @@ -1093,6 +1300,13 @@ module JamRuby elsif parsed_wav[:type] == :clickwav file.file_type = 'ClickWav' addt_files << file + + # and also add a JamTrackTrack for this click track + track.track_type = 'Click' + track.part = 'Clicktrack' + track.instrument_id = 'computer' + track.position = 10000 + tracks << track elsif parsed_wav[:type] == :precount file.file_type = 'Precount' file.precount_num = parsed_wav[:precount_num] @@ -1142,97 +1356,8 @@ module JamRuby begin Dir.mktmpdir do |tmp_dir| - generate_mp3_aac_stem(jam_track, tmp_dir, skip_audio_upload) jam_track.jam_track_tracks.each do |track| - - basename = File.basename(track.original_audio_s3_path) - s3_dirname = File.dirname(track.original_audio_s3_path) - - # make a 44100 version, and a 48000 version - ogg_44100_filename = File.basename(basename, ".wav") + "-44100.ogg" - ogg_48000_filename = File.basename(basename, ".wav") + "-48000.ogg" - - ogg_44100_s3_path = track.filename(ogg_44100_filename) - ogg_48000_s3_path = track.filename(ogg_48000_filename) - - track.skip_uploader = true - - if skip_audio_upload - track["url_44"] = ogg_44100_s3_path - track["md5_44"] = 'md5' - track["length_44"] = 1 - - track["url_48"] = ogg_48000_s3_path - track["md5_48"] = 'md5' - track["length_48"] = 1 - - # we can't fake the preview as easily because we don't know the MD5 of the current item - #track["preview_md5"] = 'md5' - #track["preview_mp3_md5"] = 'md5' - #track["preview_url"] = track.preview_filename('md5', 'ogg') - #track["preview_length"] = 1 - #track["preview_mp3_url"] = track.preview_filename('md5', 'mp3') - #track["preview_mp3_length"] = 1 - #track["preview_start_time"] = 0 - else - wav_file = File.join(tmp_dir, basename) - - # bring the original wav file down from S3 to local file system - JamTrackImporter::song_storage_manager.download(track.original_audio_s3_path, wav_file) - - sample_rate = `soxi -r "#{wav_file}"`.strip - - ogg_44100 = File.join(tmp_dir, ogg_44100_filename) - ogg_48000 = File.join(tmp_dir, File.basename(basename, ".wav") + "-48000.ogg") - - if sample_rate == "44100" - `oggenc "#{wav_file}" -q 6 -o "#{ogg_44100}"` - else - `oggenc "#{wav_file}" --resample 44100 -q 6 -o "#{ogg_44100}"` - end - - if sample_rate == "48000" - `oggenc "#{wav_file}" -q 6 -o "#{ogg_48000}"` - else - `oggenc "#{wav_file}" --resample 48000 -q 6 -o "#{ogg_48000}"` - end - - # upload the new ogg files to s3 - @@log.debug("uploading 44100 to #{ogg_44100_s3_path}") - - jamkazam_s3_manager.upload(ogg_44100_s3_path, ogg_44100) - - @@log.debug("uploading 48000 to #{ogg_48000_s3_path}") - - jamkazam_s3_manager.upload(ogg_48000_s3_path, ogg_48000) - - ogg_44100_digest = ::Digest::MD5.file(ogg_44100) - # and finally update the JamTrackTrack with the new info - track["url_44"] = ogg_44100_s3_path - track["md5_44"] = ogg_44100_digest.hexdigest - track["length_44"] = File.new(ogg_44100).size - - track["url_48"] = ogg_48000_s3_path - track["md5_48"] = ::Digest::MD5.file(ogg_48000).hexdigest - track["length_48"] = File.new(ogg_48000).size - - synchronize_duration(jam_track, ogg_44100) - jam_track.save! - - # convert entire master ogg file to mp3, and push both to public destination - if track.track_type == 'Master' - preview_succeeded = synchronize_master_preview(track, tmp_dir, ogg_44100, ogg_44100_digest) - - if !preview_succeeded - return false - end - elsif track.track_type == 'Track' - synchronize_track_preview(track, tmp_dir, ogg_44100) - end - - end - - track.save! + synchronize_audio_track(jam_track, tmp_dir, skip_audio_upload, track) end end rescue Exception => e @@ -1243,6 +1368,139 @@ module JamRuby return true end + def synchronize_audio_track(jam_track, tmp_dir, skip_audio_upload, track) + basename = File.basename(track.original_audio_s3_path) + + # make a 44100 version, and a 48000 version + ogg_44100_filename = File.basename(basename, ".wav") + "-44100.ogg" + ogg_48000_filename = File.basename(basename, ".wav") + "-48000.ogg" + + + # make a 44100 version, and a 48000 version + mp3_48000_filename = File.basename(basename, ".wav") + "-48000.mp3" + aac_48000_filename = File.basename(basename, ".wav") + "-48000.aac" + + ogg_44100_s3_path = track.filename(ogg_44100_filename) + ogg_48000_s3_path = track.filename(ogg_48000_filename) + + mp3_48000_s3_path = track.filename(mp3_48000_filename) + aac_48000_s3_path = track.filename(aac_48000_filename) + + track.skip_uploader = true + + if skip_audio_upload + track["url_44"] = ogg_44100_s3_path + track["md5_44"] = 'md5' + track["length_44"] = 1 + + track["url_48"] = ogg_48000_s3_path + track["md5_48"] = 'md5' + track["length_48"] = 1 + + track["url_mp3_48"] = mp3_48000_filename + track["md5_mp3_48"] = 'md5' + track["length_mp3_48"] = 1 + + track["url_aac_48"] = aac_48000_filename + track["md5_aac_48"] = 'md5' + track["length_aac_48"] = 1 + + # we can't fake the preview as easily because we don't know the MD5 of the current item + #track["preview_md5"] = 'md5' + #track["preview_mp3_md5"] = 'md5' + #track["preview_url"] = track.preview_filename('md5', 'ogg') + #track["preview_length"] = 1 + #track["preview_mp3_url"] = track.preview_filename('md5', 'mp3') + #track["preview_mp3_length"] = 1 + #track["preview_start_time"] = 0 + else + wav_file = File.join(tmp_dir, basename) + + # bring the original wav file down from S3 to local file system + JamTrackImporter::song_storage_manager.download(track.original_audio_s3_path, wav_file) + + sample_rate = `soxi -r "#{wav_file}"`.strip + + ogg_44100 = File.join(tmp_dir, ogg_44100_filename) + ogg_48000 = File.join(tmp_dir, File.basename(basename, ".wav") + "-48000.ogg") + + if sample_rate == "44100" + `oggenc "#{wav_file}" -q 6 -o "#{ogg_44100}"` + else + `oggenc "#{wav_file}" --resample 44100 -q 6 -o "#{ogg_44100}"` + end + + if sample_rate == "48000" + `oggenc "#{wav_file}" -q 6 -o "#{ogg_48000}"` + else + `oggenc "#{wav_file}" --resample 48000 -q 6 -o "#{ogg_48000}"` + end + + # upload the new ogg files to s3 + @@log.debug("uploading 44100 to #{ogg_44100_s3_path}") + + jamkazam_s3_manager.upload(ogg_44100_s3_path, ogg_44100) + + @@log.debug("uploading 48000 to #{ogg_48000_s3_path}") + + jamkazam_s3_manager.upload(ogg_48000_s3_path, ogg_48000) + + ogg_44100_digest = ::Digest::MD5.file(ogg_44100) + # and finally update the JamTrackTrack with the new info + track["url_44"] = ogg_44100_s3_path + track["md5_44"] = ogg_44100_digest.hexdigest + track["length_44"] = File.new(ogg_44100).size + + track["url_48"] = ogg_48000_s3_path + track["md5_48"] = ::Digest::MD5.file(ogg_48000).hexdigest + track["length_48"] = File.new(ogg_48000).size + + # now create mp3 and aac files + mp3_48000 = File.join(tmp_dir, File.basename(basename, ".wav") + "-48000.mp3") + aac_48000 = File.join(tmp_dir, File.basename(basename, ".wav") + "-48000.aac") + + `ffmpeg -i "#{wav_file}" -ar 48000 -ab 192k "#{mp3_48000}"` + + `ffmpeg -i "#{wav_file}" -c:a libfdk_aac -b:a 192k "#{aac_48000}"` + + # upload the new ogg files to s3 + @@log.debug("uploading mp3 48000 to #{mp3_48000_s3_path}") + + jamkazam_s3_manager.upload(mp3_48000_s3_path, mp3_48000) + + @@log.debug("uploading aac 48000 to #{aac_48000_s3_path}") + + jamkazam_s3_manager.upload(aac_48000_s3_path, aac_48000) + + mp3_48000_digest = ::Digest::MD5.file(mp3_48000) + # and finally update the JamTrackTrack with the new info + track["url_mp3_48"] = mp3_48000_s3_path + track["md5_mp3_48"] = mp3_48000_digest.hexdigest + track["length_mp3_48"] = File.new(mp3_48000).size + + track["url_aac_48"] = aac_48000_s3_path + track["md5_aac_48"] = ::Digest::MD5.file(aac_48000).hexdigest + track["length_aac_48"] = File.new(aac_48000).size + + synchronize_duration(jam_track, ogg_44100) + jam_track.save! + + # convert entire master ogg file to mp3, and push both to public destination + if track.track_type == 'Master' + preview_succeeded = synchronize_master_preview(track, tmp_dir, ogg_44100, ogg_44100_digest) + + if !preview_succeeded + return false + end + elsif track.track_type == 'Track' || track.track_type == 'Click' + synchronize_track_preview(track, tmp_dir, ogg_44100) + end + + end + + track.save! + end + def generate_mp3_aac_stem(jam_track, tmp_dir, skip_audio_upload) jam_track.jam_track_tracks.each do |track| @@ -1252,6 +1510,7 @@ module JamRuby next end + puts "track.original_audio_s3_path #{track.original_audio_s3_path}" basename = File.basename(track.original_audio_s3_path) s3_dirname = File.dirname(track.original_audio_s3_path) @@ -1262,6 +1521,7 @@ module JamRuby mp3_48000_s3_path = track.filename(mp3_48000_filename) aac_48000_s3_path = track.filename(aac_48000_filename) + puts "mp3_48000_s3_path #{mp3_48000_s3_path}" track.skip_uploader = true if skip_audio_upload @@ -1341,58 +1601,15 @@ module JamRuby def synchronize_track_preview(track, tmp_dir, ogg_44100) - out_wav = File.join(tmp_dir, 'stripped.wav') - - burp_gaps = ['0.3', '0.2', '0.1', '0.05'] - - total_time_command = "soxi -D \"#{ogg_44100}\"" - total_time = `#{total_time_command}`.to_f - - result_code = -20 - stripped_time = total_time # default to the case where we just start the preview at the beginning - - burp_gaps.each do |gap| - command_strip_lead_silence = "sox \"#{ogg_44100}\" \"#{out_wav}\" silence 1 #{gap} 1%" - - @@log.debug("stripping silence: " + command_strip_lead_silence) - - output = `#{command_strip_lead_silence}` - - result_code = $?.to_i - - if result_code == 0 - stripped_time_command = "soxi -D \"#{out_wav}\"" - stripped_time_test = `#{stripped_time_command}`.to_f - - if stripped_time_test < 1 # meaning a very short duration - @@log.warn("could not determine the start of non-silencea. assuming beginning") - stripped_time = total_time # default to the case where we just start the preview at the beginning - else - stripped_time = stripped_time_test # accept the measured time of the stripped file and move on by using break - break - end - else - @@log.warn("unable to determine silence for jam_track #{track.original_filename}, #{output}") - stripped_time = total_time # default to the case where we just start the preview at the beginning - end - - end - - preview_start_time = total_time - stripped_time + preview_start_time = determine_start_time(ogg_44100, tmp_dir, track.original_filename) # this is in seconds; convert to integer milliseconds preview_start_time = (preview_start_time * 1000).to_i - preview_start_time = nil if preview_start_time < 0 + preview_start_time = 0 if preview_start_time < 0 track.preview_start_time = preview_start_time - if track.preview_start_time - @@log.debug("determined track start time to be #{track.preview_start_time}") - else - @@log.debug("determined track start time to be #{track.preview_start_time}") - end - track.process_preview(ogg_44100, tmp_dir) if track.preview_start_time if track.preview_generate_error @@ -1606,6 +1823,8 @@ module JamRuby def song_storage_manager if is_tency_storage? tency_s3_manager + elsif is_tim_tracks_storage? + tim_tracks_s3_manager else s3_manager end @@ -1619,6 +1838,10 @@ module JamRuby @tency_s3_manager ||= S3Manager.new('jamkazam-tency', APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key) end + def tim_tracks_s3_manager + @tim_tracks_s3_manager ||= S3Manager.new('jamkazam-timtracks', APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key) + end + def s3_manager @s3_manager ||= S3Manager.new(APP_CONFIG.aws_bucket_jamtracks, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key) end @@ -1657,10 +1880,32 @@ module JamRuby @storage_format == 'Tency' end + def is_tim_tracks_storage? + assert_storage_set + @storage_format == 'TimTracks' + end + def assert_storage_set raise "no storage_format set" if @storage_format.nil? end + def iterate_tim_tracks_song_storage(&blk) + count = 0 + song_storage_manager.list_directories('mapped').each do |song| + @@log.debug("searching through song directory '#{song}'") + + metalocation = "#{song}meta.yml" + + metadata = load_metalocation(metalocation) + + blk.call(metadata, metalocation) + + count += 1 + #break if count > 100 + + end + end + def iterate_tency_song_storage(&blk) count = 0 song_storage_manager.list_directories('mapped').each do |song| @@ -1700,6 +1945,10 @@ module JamRuby iterate_tency_song_storage do |metadata, metalocation| blk.call(metadata, metalocation) end + elsif is_tim_tracks_storage? + iterate_tim_tracks_song_storage do |metadata, metalocation| + blk.call(metadata, metalocation) + end else iterate_default_song_storage do |metadata, metalocation| blk.call(metadata, metalocation) @@ -1915,6 +2164,73 @@ module JamRuby end end + def import_click_track(jam_track) + importer = JamTrackImporter.new + importer.name = jam_track.name + importer.import_click_track(jam_track) + + importer + end + def generate_jmep(jam_track) + importer = JamTrackImporter.new + importer.name = jam_track.name + importer.generate_jmep(jam_track) + + importer + end + + def import_click_tracks + importers = [] + + JamTrack.all.each do |jam_track| + #jam_track = JamTrack.find('126') + importers << import_click_track(jam_track) + end + + @@log.info("SUMMARY") + @@log.info("-------") + importers.each do |importer| + if importer + if importer.reason == "success" + @@log.info("#{importer.name} #{importer.reason}") + else + @@log.error("#{importer.name} failed to generate jmep.") + @@log.error("#{importer.name} reason=#{importer.reason}") + @@log.error("#{importer.name} detail=#{importer.detail}") + end + else + @@log.error("NULL IMPORTER") + end + + end + end + + def generate_jmeps + importers = [] + + JamTrack.all.each do |jam_track| + importers << generate_jmep(jam_track) + end + + @@log.info("SUMMARY") + @@log.info("-------") + importers.each do |importer| + if importer + if importer.reason == "success" + @@log.info("#{importer.name} #{importer.reason}") + else + @@log.error("#{importer.name} failed to generate jmep.") + @@log.error("#{importer.name} reason=#{importer.reason}") + @@log.error("#{importer.name} detail=#{importer.detail}") + end + else + @@log.error("NULL IMPORTER") + end + + end + end + + def synchronize_previews importers = [] @@ -2328,7 +2644,12 @@ module JamRuby else begin data = s3_manager.read_all(metalocation) - return YAML.load(data) + meta = YAML.load(data) + + if is_tim_tracks_storage? + meta[:genres] = ['acapella'] + end + meta rescue AWS::S3::Errors::NoSuchKey return nil end @@ -2395,3 +2716,4 @@ module JamRuby end end end + diff --git a/ruby/lib/jam_ruby/jam_tracks_manager.rb b/ruby/lib/jam_ruby/jam_tracks_manager.rb index d6ae37b1e..96ccf87f8 100644 --- a/ruby/lib/jam_ruby/jam_tracks_manager.rb +++ b/ruby/lib/jam_ruby/jam_tracks_manager.rb @@ -41,7 +41,7 @@ module JamRuby jam_file_opts="" jam_track.jam_track_tracks.each do |jam_track_track| - next if jam_track_track.track_type != "Track" # master mixes do not go into the JKZ + next if jam_track_track.track_type == "Master" # master mixes do not go into the JKZ # use the jam_track_track ID as the filename.ogg/.wav, because it's important metadata nm = jam_track_track.id + File.extname(jam_track_track.url_by_sample_rate(sample_rate)) @@ -52,7 +52,8 @@ module JamRuby step = bump_step(jam_track_right, step) copy_url_to_file(track_url, track_filename) - jam_file_opts << " -i #{Shellwords.escape("#{track_filename}+#{jam_track_track.part}")}" + part = jam_track_track.track_type == 'Click' ? 'ClickTrack' : jam_track_track.part + jam_file_opts << " -i #{Shellwords.escape("#{track_filename}+#{part}")}" end #puts "LS + " + `ls -la '#{tmp_dir}'` diff --git a/ruby/lib/jam_ruby/models/affiliate_partner.rb b/ruby/lib/jam_ruby/models/affiliate_partner.rb index db41ddd12..e88aed9c7 100644 --- a/ruby/lib/jam_ruby/models/affiliate_partner.rb +++ b/ruby/lib/jam_ruby/models/affiliate_partner.rb @@ -119,18 +119,16 @@ class JamRuby::AffiliatePartner < ActiveRecord::Base end def should_attribute_sale?(shopping_cart) - if shopping_cart.is_jam_track? - if created_within_affiliate_window(shopping_cart.user, Time.now) - product_info = shopping_cart.product_info - # subtract the total quantity from the freebie quantity, to see how much we should attribute to them - real_quantity = product_info[:quantity].to_i - product_info[:marked_for_redeem].to_i - {fee_in_cents: (1.99 * 100 * real_quantity * rate.to_f).round} - else - false - end + + if created_within_affiliate_window(shopping_cart.user, Time.now) + product_info = shopping_cart.product_info + # subtract the total quantity from the freebie quantity, to see how much we should attribute to them + real_quantity = product_info[:quantity].to_i - product_info[:marked_for_redeem].to_i + {fee_in_cents: (product_info[:price] * 100 * real_quantity * rate.to_f).round} else - raise 'shopping cart type not implemented yet' + false end + end def cumulative_earnings_in_dollars diff --git a/ruby/lib/jam_ruby/models/anonymous_user.rb b/ruby/lib/jam_ruby/models/anonymous_user.rb index 24bc2b104..55c788e90 100644 --- a/ruby/lib/jam_ruby/models/anonymous_user.rb +++ b/ruby/lib/jam_ruby/models/anonymous_user.rb @@ -15,20 +15,47 @@ module JamRuby ShoppingCart.where(anonymous_user_id: @id).order('created_at DESC') end + def destroy_all_shopping_carts ShoppingCart.destroy_all(anonymous_user_id: @id) end + def destroy_jam_track_shopping_carts + ShoppingCart.destroy_all(anonymous_user_id: @id, cart_type: JamTrack::PRODUCT_TYPE) + end + def admin false end def has_redeemable_jamtrack + raise "not a cookied anonymous user" if @cookies.nil? + APP_CONFIG.one_free_jamtrack_per_user && !@cookies[:redeemed_jamtrack] end + def gifted_jamtracks + 0 + end + + def free_jamtracks + if has_redeemable_jamtrack + 1 + else + 0 + end + end + + def show_free_jamtrack? + ShoppingCart.user_has_redeemable_jam_track?(self) + end + def signup_hint SignupHint.where(anonymous_user_id: @id).where('expires_at > ?', Time.now).first end + + def reload + + end end end diff --git a/ruby/lib/jam_ruby/models/download_tracker.rb b/ruby/lib/jam_ruby/models/download_tracker.rb index f31001504..6a2bcf301 100644 --- a/ruby/lib/jam_ruby/models/download_tracker.rb +++ b/ruby/lib/jam_ruby/models/download_tracker.rb @@ -97,6 +97,7 @@ module JamRuby body = "IP Address: #{remote_ip}\n" body << "Download Count: #{violation['count']}\n" body << "Add to blacklist: #{IpBlacklist.admin_url}" + body << "Check Activity: #{IpBlacklist.admin_activity_url(remote_ip)}" AdminMailer.alerts({ subject:"Single IP Access Violation. IP:#{remote_ip}", diff --git a/ruby/lib/jam_ruby/models/gift_card.rb b/ruby/lib/jam_ruby/models/gift_card.rb new file mode 100644 index 000000000..0dc5f94e8 --- /dev/null +++ b/ruby/lib/jam_ruby/models/gift_card.rb @@ -0,0 +1,36 @@ +# represents the gift card you hold in your hand +module JamRuby + class GiftCard < ActiveRecord::Base + + @@log = Logging.logger[GiftCard] + + JAM_TRACKS_5 = 'jam_tracks_5' + JAM_TRACKS_10 = 'jam_tracks_10' + CARD_TYPES = + [ + JAM_TRACKS_5, + JAM_TRACKS_10 + ] + + + belongs_to :user, class_name: "JamRuby::User" + + validates :card_type, presence: true, inclusion: {in: CARD_TYPES} + validates :code, presence: true, uniqueness: true + + after_save :check_gifted + + def check_gifted + if user && user_id_changed? + if card_type == JAM_TRACKS_5 + user.gifted_jamtracks += 5 + elsif card_type == JAM_TRACKS_10 + user.gifted_jamtracks += 10 + else + raise "unknown card type #{card_type}" + end + user.save! + end + end + end +end diff --git a/ruby/lib/jam_ruby/models/gift_card_purchase.rb b/ruby/lib/jam_ruby/models/gift_card_purchase.rb new file mode 100644 index 000000000..0cfb00807 --- /dev/null +++ b/ruby/lib/jam_ruby/models/gift_card_purchase.rb @@ -0,0 +1,17 @@ +# reperesents the gift card you buy from the site (but physical gift card is modeled by GiftCard) +module JamRuby + class GiftCardPurchase < ActiveRecord::Base + + @@log = Logging.logger[GiftCardPurchase] + + attr_accessible :user, :gift_card_type + + def name + gift_card_type.sale_display + end + + # who purchased the card? + belongs_to :user, class_name: "JamRuby::User" + belongs_to :gift_card_type, class_name: "JamRuby::GiftCardType" + end +end diff --git a/ruby/lib/jam_ruby/models/gift_card_type.rb b/ruby/lib/jam_ruby/models/gift_card_type.rb new file mode 100644 index 000000000..8294dbfed --- /dev/null +++ b/ruby/lib/jam_ruby/models/gift_card_type.rb @@ -0,0 +1,70 @@ +# reperesents the gift card you buy from the site (but physical gift card is modeled by GiftCard) +module JamRuby + class GiftCardType < ActiveRecord::Base + + @@log = Logging.logger[GiftCardType] + + PRODUCT_TYPE = 'GiftCardType' + + JAM_TRACKS_5 = 'jam_tracks_5' + JAM_TRACKS_10 = 'jam_tracks_10' + CARD_TYPES = + [ + JAM_TRACKS_5, + JAM_TRACKS_10 + ] + + validates :card_type, presence: true, inclusion: {in: CARD_TYPES} + + def self.jam_track_5 + GiftCardType.find(JAM_TRACKS_5) + end + + def self.jam_track_10 + GiftCardType.find(JAM_TRACKS_10) + end + + def name + sale_display + end + + def price + if card_type == JAM_TRACKS_5 + 10.00 + elsif card_type == JAM_TRACKS_10 + 20.00 + else + raise "unknown card type #{card_type}" + end + end + + + def sale_display + if card_type == JAM_TRACKS_5 + 'JamTracks Gift Card (5)' + elsif card_type == JAM_TRACKS_10 + 'JamTracks Gift Card (10)' + else + raise "unknown card type #{card_type}" + end + end + + def plan_code + if card_type == JAM_TRACKS_5 + "jamtrack-giftcard-5" + elsif card_type == JAM_TRACKS_10 + "jamtrack-giftcard-10" + else + raise "unknown card type #{card_type}" + end + end + + def sales_region + 'Worldwide' + end + + def to_s + sale_display + end + end +end diff --git a/ruby/lib/jam_ruby/models/ip_blacklist.rb b/ruby/lib/jam_ruby/models/ip_blacklist.rb index e62020e26..a7099cba8 100644 --- a/ruby/lib/jam_ruby/models/ip_blacklist.rb +++ b/ruby/lib/jam_ruby/models/ip_blacklist.rb @@ -15,6 +15,10 @@ module JamRuby APP_CONFIG.admin_root_url + "/admin/ip_blacklists/" end + def self.admin_activity_url(remote_ip) + APP_CONFIG.admin_root_url + "/admin/download_trackers?q[remote_ip_equals]=#{URI.escape(remote_ip)}&commit=Filter&order=id_desc" + end + def admin_url APP_CONFIG.admin_root_url + "/admin/ip_blacklists/" + id end diff --git a/ruby/lib/jam_ruby/models/jam_track.rb b/ruby/lib/jam_ruby/models/jam_track.rb index bf200cfd7..4e3e63fe5 100644 --- a/ruby/lib/jam_ruby/models/jam_track.rb +++ b/ruby/lib/jam_ruby/models/jam_track.rb @@ -19,7 +19,7 @@ module JamRuby :reproduction_royalty, :public_performance_royalty, :reproduction_royalty_amount, :licensor_royalty_amount, :pro_royalty_amount, :plan_code, :initial_play_silence, :jam_track_tracks_attributes, :jam_track_tap_ins_attributes, :genre_ids, :version, :jmep_json, :jmep_text, :pro_ascap, :pro_bmi, :pro_sesac, :duration, - :server_fixation_date, :hfa_license_status, :hfa_license_desired, :alternative_license_status, :hfa_license_number, :hfa_song_code, :album_title, as: :admin + :server_fixation_date, :hfa_license_status, :hfa_license_desired, :alternative_license_status, :hfa_license_number, :hfa_song_code, :album_title, :year, as: :admin validates :name, presence: true, length: {maximum: 200} validates :plan_code, presence: true, uniqueness: true, length: {maximum: 50 } @@ -86,6 +86,10 @@ module JamRuby after_save :sync_reproduction_royalty after_save :sync_onboarding_exceptions + def increment_version! + self.version = version.to_i + 1 + save! + end def sync_reproduction_royalty @@ -155,6 +159,9 @@ module JamRuby true end + def sale_display + "JamTrack: " + name + end def duplicate_positions? counter = {} jam_track_tracks.each do |track| @@ -338,7 +345,7 @@ module JamRuby query = query.where('genre_id = ? ', options[:genre]) end - query = query.where("jam_track_tracks.instrument_id = '#{options[:instrument]}' and jam_track_tracks.track_type != 'Master'") unless options[:instrument].blank? + query = query.where("jam_track_tracks.instrument_id = '#{options[:instrument]}' and jam_track_tracks.track_type = 'Track'") unless options[:instrument].blank? query = query.where("jam_tracks.sales_region = '#{options[:availability]}'") unless options[:availability].blank? # FIXME: n+1 queries for rights and genres @@ -347,9 +354,8 @@ module JamRuby # :jam_track_rights, # :genres]) # { genres_jam_tracks: :genre }, - query = query.includes([{ jam_track_tracks: :instrument }, - { genres_jam_tracks: :genre }, - :jam_track_tap_ins]) + # query = query.includes([{ jam_track_tracks: :instrument }, + # { genres_jam_tracks: :genre }]) count = query.total_entries @@ -432,13 +438,20 @@ module JamRuby end end + def click_track_file + JamTrackFile.where(jam_track_id: self.id).where(file_type: 'ClickWav').first + end + + def click_track + JamTrackTrack.where(jam_track_id: self.id).where(track_type: 'Click').first + end def master_track JamTrackTrack.where(jam_track_id: self.id).where(track_type: 'Master').first end def stem_tracks - JamTrackTrack.where(jam_track_id: self.id).where(track_type: 'Track') + JamTrackTrack.where(jam_track_id: self.id).where("track_type = 'Track' or track_type = 'Click'") end def can_download?(user) @@ -465,6 +478,27 @@ module JamRuby def generate_slug self.slug = sluggarize(original_artist) + '-' + sluggarize(name) + + if licensor + raise "no slug on licensor #{licensor.id}" if licensor.slug.nil? + self.slug << "-" + licensor.slug + end + end + + def gen_plan_code + # remove all non-alphanumeric chars from artist as well as name + artist_code = original_artist.gsub(/[^0-9a-z]/i, '').downcase + name_code = name.gsub(/[^0-9a-z]/i, '').downcase + self.plan_code = "jamtrack-#{artist_code[0...20]}-#{name_code}" + + if licensor + raise "no slug on licensor #{licensor.id}" if licensor.slug.nil? + self.plan_code << "-" + licensor.slug + end + + self.plan_code = self.plan_code[0...50] # make sure it's a max of 50 long + + end def to_s @@ -492,5 +526,30 @@ module JamRuby Digest::MD5.hexdigest(dates) end + + attr_accessor :preview_generate_error + + before_save :jmep_json_generate + validate :jmep_text_validate + + def jmep_text_validate + begin + JmepManager.execute(self.jmep_text) + rescue ArgumentError => err + errors.add(:jmep_text, err.to_s) + end + end + + def jmep_json_generate + self.licensor_id = nil if self.licensor_id == '' + self.jmep_json = nil if self.jmep_json == '' + self.time_signature = nil if self.time_signature == '' + + begin + self[:jmep_json] = JmepManager.execute(self.jmep_text) + rescue ArgumentError => err + #errors.add(:jmep_text, err.to_s) + end + end end end diff --git a/ruby/lib/jam_ruby/models/jam_track_file.rb b/ruby/lib/jam_ruby/models/jam_track_file.rb index e7c880165..e31a95ba2 100644 --- a/ruby/lib/jam_ruby/models/jam_track_file.rb +++ b/ruby/lib/jam_ruby/models/jam_track_file.rb @@ -27,9 +27,18 @@ module JamRuby "jam_track_files" end + def licensor_suffix + suffix = '' + if jam_track.licensor + raise "no licensor name" if jam_track.licensor.name.nil? + suffix = " - #{jam_track.licensor.name}" + end + suffix + end + # create name of the file def filename(original_name) - "#{store_dir}/#{jam_track.original_artist}/#{jam_track.name}/#{original_name}" + "#{store_dir}/#{jam_track.original_artist}/#{jam_track.name}#{licensor_suffix}/#{original_name}" end def manually_uploaded_filename diff --git a/ruby/lib/jam_ruby/models/jam_track_licensor.rb b/ruby/lib/jam_ruby/models/jam_track_licensor.rb index e06e292bc..776418159 100644 --- a/ruby/lib/jam_ruby/models/jam_track_licensor.rb +++ b/ruby/lib/jam_ruby/models/jam_track_licensor.rb @@ -4,7 +4,7 @@ module JamRuby table_name = 'jam_track_licensors' attr_accessible :name, :description, :attention, :address_line_1, :address_line_2, - :city, :state, :zip_code, :contact, :email, :phone, as: :admin + :city, :state, :zip_code, :contact, :email, :phone, :slug, as: :admin validates :name, presence: true, uniqueness: true, length: {maximum: 200} validates :description, length: {maximum: 1000} diff --git a/ruby/lib/jam_ruby/models/jam_track_mixdown.rb b/ruby/lib/jam_ruby/models/jam_track_mixdown.rb index 3551dcdba..c1a30d073 100644 --- a/ruby/lib/jam_ruby/models/jam_track_mixdown.rb +++ b/ruby/lib/jam_ruby/models/jam_track_mixdown.rb @@ -83,6 +83,11 @@ module JamRuby end end + if parsed["count-in"] + all_quiet = false + tweaked = true + end + if all_quiet errors.add(:settings, 'are all muted') end diff --git a/ruby/lib/jam_ruby/models/jam_track_right.rb b/ruby/lib/jam_ruby/models/jam_track_right.rb index 808593322..63558f84a 100644 --- a/ruby/lib/jam_ruby/models/jam_track_right.rb +++ b/ruby/lib/jam_ruby/models/jam_track_right.rb @@ -14,6 +14,7 @@ module JamRuby belongs_to :last_mixdown, class_name: 'JamRuby::JamTrackMixdown', foreign_key: 'last_mixdown_id', inverse_of: :jam_track_right belongs_to :last_stem, class_name: 'JamRuby::JamTrackTrack', foreign_key: 'last_stem_id', inverse_of: :jam_track_right + validates :version, presence: true validates :user, presence: true validates :jam_track, presence: true validates :is_test_purchase, inclusion: {in: [true, false]} @@ -133,8 +134,33 @@ module JamRuby end end + def cleanup_old_package! + if self.jam_track.version != self.version + delete_s3_files + self[:url_48] = nil + self[:url_44] = nil + self.signing_queued_at = nil + self.signing_started_at_48 = nil + self.signing_started_at_44 = nil + self.last_signed_at = nil + self.current_packaging_step = nil + self.packaging_steps = nil + self.should_retry = false + self.signing_44 = false + self.signing_48 = false + self.signed_44 = false + self.signed_48 = false + self.queued = false + self.version = self.jam_track.version + self.save! + end + end # if the job is already signed, just queued up for signing, or currently signing, then don't enqueue... otherwise fire it off def enqueue_if_needed(sample_rate=48) + + # delete any package that's out dated + cleanup_old_package! + state = signing_state(sample_rate) if state == 'SIGNED' || state == 'SIGNING' || state == 'QUEUED' false @@ -148,9 +174,9 @@ module JamRuby # @return true if signed && file exists for the sample_rate specifed: def ready?(sample_rate=48) if sample_rate==48 - self.signed_48 && self.url_48.present? && self.url_48.file.exists? + self.signed_48 && self.url_48.present? && self.url_48.file.exists? && self.version == self.jam_track.version else - self.signed_44 && self.url_44.present? && self.url_44.file.exists? + self.signed_44 && self.url_44.present? && self.url_44.file.exists? && self.version == self.jam_track.version end end diff --git a/ruby/lib/jam_ruby/models/jam_track_search.rb b/ruby/lib/jam_ruby/models/jam_track_search.rb index 7a365ed60..4bbcde75b 100644 --- a/ruby/lib/jam_ruby/models/jam_track_search.rb +++ b/ruby/lib/jam_ruby/models/jam_track_search.rb @@ -75,7 +75,7 @@ module JamRuby sqlstr = "'#{instruments.join("','")}'" rel = rel.joins(:jam_track_tracks) rel = rel.where("jam_track_tracks.instrument_id IN (#{sqlstr})") - rel = rel.where("jam_track_tracks.track_type != 'Master'") + rel = rel.where("jam_track_tracks.track_type = 'Track'") end end diff --git a/ruby/lib/jam_ruby/models/jam_track_track.rb b/ruby/lib/jam_ruby/models/jam_track_track.rb index 22e8f17f3..989f62a66 100644 --- a/ruby/lib/jam_ruby/models/jam_track_track.rb +++ b/ruby/lib/jam_ruby/models/jam_track_track.rb @@ -6,7 +6,7 @@ module JamRuby include JamRuby::S3PublicManagerMixin # there should only be one Master per JamTrack, but there can be N Track per JamTrack - TRACK_TYPE = %w{Track Master} + TRACK_TYPE = %w{Track Master Click} @@log = Logging.logger[JamTrackTrack] @@ -41,9 +41,19 @@ module JamRuby "jam_track_tracks" end + + def licensor_suffix + suffix = '' + if jam_track.licensor + raise "no licensor name" if jam_track.licensor.name.nil? + suffix = " - #{jam_track.licensor.name}" + end + suffix + end + # create name of the file def filename(original_name) - "#{store_dir}/#{jam_track.original_artist}/#{jam_track.name}/#{original_name}" + "#{store_dir}/#{jam_track.original_artist}/#{jam_track.name}#{licensor_suffix}/#{original_name}" end # create name of the preview file. @@ -54,7 +64,7 @@ module JamRuby end def preview_directory - "jam_track_previews/#{jam_track.original_artist}/#{jam_track.name}" + "jam_track_previews/#{jam_track.original_artist}/#{jam_track.name}#{licensor_suffix}" end def has_preview? @@ -192,10 +202,10 @@ module JamRuby # input is the original ogg file for the track. tmp_dir is where this code can safely generate output stuff and have it cleaned up later def process_preview(input, tmp_dir) - raise "Does not include AAC generation. Must be updated before used." uuid = SecureRandom.uuid output = File.join(tmp_dir, "#{uuid}.ogg") output_mp3 = File.join(tmp_dir, "#{uuid}.mp3") + output_aac = File.join(tmp_dir, "#{uuid}.aac") start = self.preview_start_time.to_f / 1000 stop = start + 20 @@ -225,35 +235,55 @@ module JamRuby @@log.debug("fail #{result_code}") @preview_generate_error = "unable to execute mp3 convert command #{convert_output}" else - ogg_digest = ::Digest::MD5.file(output) - mp3_digest = ::Digest::MD5.file(output_mp3) - self["preview_md5"] = ogg_md5 = ogg_digest.hexdigest - self["preview_mp3_md5"] = mp3_md5 = mp3_digest.hexdigest - @@log.debug("uploading ogg preview to #{self.preview_filename('ogg')}") - s3_public_manager.upload(self.preview_filename(ogg_md5, 'ogg'), output, content_type: 'audio/ogg', content_md5: ogg_digest.base64digest) - @@log.debug("uploading mp3 preview to #{self.preview_filename('mp3')}") - s3_public_manager.upload(self.preview_filename(mp3_md5, 'mp3'), output_mp3, content_type: 'audio/mpeg', content_md5: mp3_digest.base64digest) + convert_aac_cmd = "#{APP_CONFIG.ffmpeg_path} -i \"#{output}\" -c:a libfdk_aac -b:a 192k \"#{output_aac}\"" + @@log.debug("converting to aac using: " + convert_aac_cmd) - self.skip_uploader = true + convert_output = `#{convert_aac_cmd}` - original_ogg_preview_url = self["preview_url"] - original_mp3_preview_url = self["preview_mp3_url"] + result_code = $?.to_i - # and finally update the JamTrackTrack with the new info - self["preview_url"] = self.preview_filename(ogg_md5, 'ogg') - self["preview_length"] = File.new(output).size - # and finally update the JamTrackTrack with the new info - self["preview_mp3_url"] = self.preview_filename(mp3_md5, 'mp3') - self["preview_mp3_length"] = File.new(output_mp3).size - self.save! + if result_code != 0 + @@log.debug("fail #{result_code}") + @preview_generate_error = "unable to execute aac convert command #{convert_output}" + else - # if all that worked, now delete old previews, if present - begin - s3_public_manager.delete(original_ogg_preview_url) if original_ogg_preview_url && original_ogg_preview_url != self["preview_url"] - s3_public_manager.delete(original_mp3_preview_url) if original_mp3_preview_url && original_mp3_preview_url != track["preview_mp3_url"] - rescue - puts "UNABLE TO CLEANUP OLD PREVIEW URL" + ogg_digest = ::Digest::MD5.file(output) + mp3_digest = ::Digest::MD5.file(output_mp3) + aac_digest = ::Digest::MD5.file(output_aac) + self["preview_md5"] = ogg_md5 = ogg_digest.hexdigest + self["preview_mp3_md5"] = mp3_md5 = mp3_digest.hexdigest + self["preview_aac_md5"] = aac_md5 = mp3_digest.hexdigest + + @@log.debug("uploading ogg preview to #{self.preview_filename('ogg')}") + s3_public_manager.upload(self.preview_filename(ogg_md5, 'ogg'), output, content_type: 'audio/ogg', content_md5: ogg_digest.base64digest) + @@log.debug("uploading mp3 preview to #{self.preview_filename('mp3')}") + s3_public_manager.upload(self.preview_filename(mp3_md5, 'mp3'), output_mp3, content_type: 'audio/mpeg', content_md5: mp3_digest.base64digest) + @@log.debug("uploading aac preview to #{self.preview_filename('aac')}") + s3_public_manager.upload(self.preview_filename(aac_md5, 'aac'), output_aac, content_type: 'audio/aac', content_md5: aac_digest.base64digest) + + self.skip_uploader = true + + original_ogg_preview_url = self["preview_url"] + original_mp3_preview_url = self["preview_mp3_url"] + original_aac_preview_url = self["preview_aac_url"] + + self["preview_url"] = self.preview_filename(ogg_md5, 'ogg') + self["preview_length"] = File.new(output).size + self["preview_mp3_url"] = self.preview_filename(mp3_md5, 'mp3') + self["preview_mp3_length"] = File.new(output_mp3).size + self["preview_aac_url"] = self.preview_filename(aac_md5, 'aac') + self["preview_aac_length"] = File.new(output_aac).size + self.save! + + # if all that worked, now delete old previews, if present + begin + s3_public_manager.delete(original_ogg_preview_url) if original_ogg_preview_url && original_ogg_preview_url != self["preview_url"] + s3_public_manager.delete(original_mp3_preview_url) if original_mp3_preview_url && original_mp3_preview_url != track["preview_mp3_url"] + s3_public_manager.delete(original_aac_preview_url) if original_aac_preview_url && original_aac_preview_url != track["preview_aac_url"] + rescue + puts "UNABLE TO CLEANUP OLD PREVIEW URL" + end end end diff --git a/ruby/lib/jam_ruby/models/recurly_transaction_web_hook.rb b/ruby/lib/jam_ruby/models/recurly_transaction_web_hook.rb index 9d0fc9bd4..006611f61 100644 --- a/ruby/lib/jam_ruby/models/recurly_transaction_web_hook.rb +++ b/ruby/lib/jam_ruby/models/recurly_transaction_web_hook.rb @@ -92,53 +92,15 @@ module JamRuby transaction.save! # now that we have the transaction saved, we also need to delete the jam_track_right if this is a refund, or voided - - if transaction.transaction_type == 'refund' || transaction.transaction_type == 'void' sale = Sale.find_by_recurly_invoice_id(transaction.invoice_id) - if sale && sale.is_jam_track_sale? - if sale.sale_line_items.length == 1 - if sale.recurly_total_in_cents == transaction.amount_in_cents - line_item = sale.sale_line_items[0] - jam_track = line_item.product - jam_track_right = jam_track.right_for_user(transaction.user) if jam_track - if jam_track_right - line_item.affiliate_refunded = true - line_item.affiliate_refunded_at = Time.now - line_item.save! + if sale + AdminMailer.recurly_alerts(transaction.user, { + subject: "ACTION REQUIRED: #{transaction.user.email} has refund on invoice", + body: "You will have to manually revoke any JamTrackRights in our database for the appropriate JamTracks" + }).deliver - jam_track_right.destroy - - # associate which JamTrack we assume this is related to in this one success case - transaction.jam_track = jam_track - transaction.save! - - AdminMailer.recurly_alerts(transaction.user, { - subject: "NOTICE: #{transaction.user.email} has had JamTrack: #{jam_track.name} revoked", - body: "A #{transaction.transaction_type} event came from Recurly for sale with Recurly invoice ID #{sale.recurly_invoice_id}. We deleted their right to the track in our own database as a result." - }).deliver - else - AdminMailer.recurly_alerts(transaction.user, { - subject: "NOTICE: #{transaction.user.email} got a refund, but unable to find JamTrackRight to delete", - body: "This should just mean the user already has no rights to the JamTrackRight when the refund came in. Not a big deal, but sort of weird..." - }).deliver - end - - else - AdminMailer.recurly_alerts(transaction.user, { - subject: "ACTION REQUIRED: #{transaction.user.email} got a refund it was not for total value of a JamTrack sale", - body: "We received a #{transaction.transaction_type} notice for an amount that was not the same as the original sale. So, no action was taken in the database. sale total: #{sale.recurly_total_in_cents}, refund amount: #{transaction.amount_in_cents}" - }).deliver - end - - - else - AdminMailer.recurly_alerts(transaction.user, { - subject: "ACTION REQUIRED: #{transaction.user.email} has refund on invoice with multiple JamTracks", - body: "You will have to manually revoke any JamTrackRights in our database for the appropriate JamTracks" - }).deliver - end else AdminMailer.recurly_alerts(transaction.user, { subject: "ACTION REQUIRED: #{transaction.user.email} has refund with no correlator to sales", diff --git a/ruby/lib/jam_ruby/models/sale.rb b/ruby/lib/jam_ruby/models/sale.rb index 70279f8eb..cddf6b8d8 100644 --- a/ruby/lib/jam_ruby/models/sale.rb +++ b/ruby/lib/jam_ruby/models/sale.rb @@ -69,27 +69,12 @@ module JamRuby } end - def self.preview_invoice(current_user, shopping_carts) - line_items = {jam_tracks: []} - shopping_carts_jam_tracks = [] - shopping_carts_subscriptions = [] - shopping_carts.each do |shopping_cart| - - if shopping_cart.is_jam_track? - shopping_carts_jam_tracks << shopping_cart - else - # XXX: this may have to be revisited when we actually have something other than JamTracks for puchase - shopping_carts_subscriptions << shopping_cart - end + def self.ios_purchase(current_user, jam_track, receipt) + jam_track_right = JamRuby::JamTrackRight.find_or_create_by_user_id_and_jam_track_id(current_user.id, jam_track.id) do |jam_track_right| + jam_track_right.redeemed = false + jam_track_right.version = jam_track.version end - - jam_track_items = preview_invoice_jam_tracks(current_user, shopping_carts_jam_tracks) - line_items[:jam_tracks] = jam_track_items if jam_track_items - - # TODO: process shopping_carts_subscriptions - - line_items end # place_order will create one or more sales based on the contents of shopping_carts for the current user @@ -99,19 +84,14 @@ module JamRuby def self.place_order(current_user, shopping_carts) sales = [] - shopping_carts_jam_tracks = [] - shopping_carts_subscriptions = [] - shopping_carts.each do |shopping_cart| - if shopping_cart.is_jam_track? - shopping_carts_jam_tracks << shopping_cart - else - # XXX: this may have to be revisited when we actually have something other than JamTracks for puchase - shopping_carts_subscriptions << shopping_cart - end + + if Sale.is_mixed(shopping_carts) + # the controller checks this too; this is just an extra-level of sanity checking + return sales end - jam_track_sale = order_jam_tracks(current_user, shopping_carts_jam_tracks) + jam_track_sale = order_jam_tracks(current_user, shopping_carts) sales << jam_track_sale if jam_track_sale # TODO: process shopping_carts_subscriptions @@ -119,22 +99,52 @@ module JamRuby sales end - def self.preview_invoice_jam_tracks(current_user, shopping_carts_jam_tracks) - ### XXX TODO; - # we currently use a fake plan in Recurly to estimate taxes using the Pricing.Attach metod in Recurly.js + def self.is_only_freebie(shopping_carts) + free = true + shopping_carts.each do |cart| + free = cart.product_info[:free] - # if we were to implement this the right way (ensure adjustments are on the account as necessary), then it would be better (more correct) - # just a pain to implement + if !free + break + end + end + free end - def self.is_only_freebie(shopping_carts_jam_tracks) - shopping_carts_jam_tracks.length == 1 && shopping_carts_jam_tracks[0].product_info[:free] + # we don't allow mixed shopping carts :/ + def self.is_mixed(shopping_carts) + free = false + non_free = false + shopping_carts.each do |cart| + if cart.product_info[:free] + free = true + else + non_free = true + end + end + free && non_free end # this method will either return a valid sale, or throw a RecurlyClientError or ActiveRecord validation error (save! failed) # it may return an nil sale if the JamTrack(s) specified by the shopping carts are already owned - def self.order_jam_tracks(current_user, shopping_carts_jam_tracks) + def self.order_jam_tracks(current_user, shopping_carts) + + shopping_carts_jam_tracks = [] + shopping_carts_subscriptions = [] + shopping_carts_gift_cards = [] + + shopping_carts.each do |shopping_cart| + if shopping_cart.is_jam_track? + shopping_carts_jam_tracks << shopping_cart + elsif shopping_cart.is_gift_card? + shopping_carts_gift_cards << shopping_cart + else + # XXX: this may have to be revisited when we actually have something other than JamTracks for puchase + raise "unknown shopping cart type #{shopping_cart.cart_type}" + shopping_carts_subscriptions << shopping_cart + end + end client = RecurlyClient.new @@ -143,8 +153,8 @@ module JamRuby sale = create_jam_track_sale(current_user) if sale.valid? - if is_only_freebie(shopping_carts_jam_tracks) - sale.process_jam_tracks(current_user, shopping_carts_jam_tracks, nil) + if is_only_freebie(shopping_carts) + sale.process_shopping_carts(current_user, shopping_carts, nil) sale.recurly_subtotal_in_cents = 0 sale.recurly_tax_in_cents = 0 @@ -159,11 +169,13 @@ module JamRuby return sale end - sale_line_item = sale.sale_line_items[0] - sale_line_item.recurly_tax_in_cents = 0 - sale_line_item.recurly_total_in_cents = 0 - sale_line_item.recurly_currency = 'USD' - sale_line_item.recurly_discount_in_cents = 0 + sale.sale_line_items.each do |sale_line_item| + sale_line_item = sale.sale_line_items[0] + sale_line_item.recurly_tax_in_cents = 0 + sale_line_item.recurly_total_in_cents = 0 + sale_line_item.recurly_currency = 'USD' + sale_line_item.recurly_discount_in_cents = 0 + end sale.save else @@ -173,7 +185,7 @@ module JamRuby purge_pending_adjustments(account) - created_adjustments = sale.process_jam_tracks(current_user, shopping_carts_jam_tracks, account) + created_adjustments = sale.process_shopping_carts(current_user, shopping_carts, account) # now invoice the sale ... almost done @@ -229,13 +241,13 @@ module JamRuby sale end - def process_jam_tracks(current_user, shopping_carts_jam_tracks, account) + def process_shopping_carts(current_user, shopping_carts, account) created_adjustments = [] begin - shopping_carts_jam_tracks.each do |shopping_cart| - process_jam_track(current_user, shopping_cart, account, created_adjustments) + shopping_carts.each do |shopping_cart| + process_shopping_cart(current_user, shopping_cart, account, created_adjustments) end rescue Recurly::Error, NoMethodError => x # rollback any adjustments created if error @@ -251,7 +263,7 @@ module JamRuby end - def process_jam_track(current_user, shopping_cart, account, created_adjustments) + def process_shopping_cart(current_user, shopping_cart, account, created_adjustments) recurly_adjustment_uuid = nil recurly_adjustment_credit_uuid = nil @@ -259,15 +271,20 @@ module JamRuby shopping_cart.reload # get the JamTrack in this shopping cart - jam_track = shopping_cart.cart_product + cart_product = shopping_cart.cart_product - if jam_track.right_for_user(current_user) - # if the user already owns the JamTrack, we should just skip this cart item, and destroy it - # if this occurs, we have to reload every shopping_cart as we iterate. so, we do at the top of the loop - ShoppingCart.remove_jam_track_from_cart(current_user, shopping_cart) - return + if shopping_cart.is_jam_track? + jam_track = cart_product + if jam_track.right_for_user(current_user) + # if the user already owns the JamTrack, we should just skip this cart item, and destroy it + # if this occurs, we have to reload every shopping_cart as we iterate. so, we do at the top of the loop + ShoppingCart.remove_jam_track_from_cart(current_user, shopping_cart) + return + end end + + if account # ask the shopping cart to create the correct Recurly adjustment attributes for a JamTrack adjustments = shopping_cart.create_adjustment_attributes(current_user) @@ -300,38 +317,70 @@ module JamRuby # if the sale line item is invalid, blow up the transaction unless sale_line_item.valid? - @log.error("sale item invalid! #{sale_line_item.errors.inspect}") + @@log.error("sale item invalid! #{sale_line_item.errors.inspect}") puts("sale item invalid! #{sale_line_item.errors.inspect}") Stats.write('web.recurly.purchase.sale_invalid', {message: sale_line_item.errors.to_s, value: 1}) raise RecurlyClientError.new(sale_line_item.errors) end - # create a JamTrackRight (this needs to be in a transaction too to make sure we don't make these by accident) - jam_track_right = JamRuby::JamTrackRight.find_or_create_by_user_id_and_jam_track_id(current_user.id, jam_track.id) do |jam_track_right| - jam_track_right.redeemed = shopping_cart.free? - end + if shopping_cart.is_jam_track? + jam_track = cart_product - # also if the purchase was a free one, then update the user record to no longer allow redeemed jamtracks - if shopping_cart.free? - User.where(id: current_user.id).update_all(has_redeemable_jamtrack: false) - current_user.has_redeemable_jamtrack = false # make sure model reflects the truth - end - - - # this can't go in the block above, as it's here to fix bad subscription UUIDs in an update path - if jam_track_right.recurly_adjustment_uuid != recurly_adjustment_uuid - jam_track_right.recurly_adjustment_uuid = recurly_adjustment_uuid - jam_track_right.recurly_adjustment_credit_uuid = recurly_adjustment_credit_uuid - unless jam_track_right.save - raise RecurlyClientError.new(jam_track_right.errors) + # create a JamTrackRight (this needs to be in a transaction too to make sure we don't make these by accident) + jam_track_right = JamRuby::JamTrackRight.find_or_create_by_user_id_and_jam_track_id(current_user.id, jam_track.id) do |jam_track_right| + jam_track_right.redeemed = shopping_cart.free? + jam_track_right.version = jam_track.version end + + # also if the purchase was a free one, then: + # first, mark the free has_redeemable_jamtrack field if that's still true + # and if still they have more free things, then redeem the giftable_jamtracks + if shopping_cart.free? + if user.has_redeemable_jamtrack + User.where(id: current_user.id).update_all(has_redeemable_jamtrack: false) + current_user.has_redeemable_jamtrack = false + else + User.where(id: current_user.id).update_all(gifted_jamtracks: current_user.gifted_jamtracks - 1) + current_user.gifted_jamtracks = current_user.gifted_jamtracks - 1 + end + end + + + # this can't go in the block above, as it's here to fix bad subscription UUIDs in an update path + if jam_track_right.recurly_adjustment_uuid != recurly_adjustment_uuid + jam_track_right.recurly_adjustment_uuid = recurly_adjustment_uuid + jam_track_right.recurly_adjustment_credit_uuid = recurly_adjustment_credit_uuid + unless jam_track_right.save + raise RecurlyClientError.new(jam_track_right.errors) + end + end + + # blow up the transaction if the JamTrackRight did not get created + raise RecurlyClientError.new(jam_track_right.errors) if jam_track_right.errors.any? + + elsif shopping_cart.is_gift_card? + gift_card_type = cart_product + raise "gift card is null" if gift_card_type.nil? + raise if current_user.nil? + + shopping_cart.quantity.times do |item| + gift_card_purchase = GiftCardPurchase.new( + { + user: current_user, + gift_card_type: gift_card_type + }) + + unless gift_card_purchase.save + raise RecurlyClientError.new(gift_card_purchase.errors) + end + end + + else + raise 'unknown shopping cart type: ' + shopping_cart.cart_type end # delete the shopping cart; it's been dealt with shopping_cart.destroy if shopping_cart - - # blow up the transaction if the JamTrackRight did not get created - raise RecurlyClientError.new(jam_track_right.errors) if jam_track_right.errors.any? end @@ -361,7 +410,7 @@ module JamRuby def self.create_jam_track_sale(user) sale = Sale.new sale.user = user - sale.sale_type = JAMTRACK_SALE + sale.sale_type = JAMTRACK_SALE # gift cards and jam tracks are sold with this type of sale sale.order_total = 0 sale.save sale diff --git a/ruby/lib/jam_ruby/models/sale_line_item.rb b/ruby/lib/jam_ruby/models/sale_line_item.rb index 7d744cfe5..5cbe5ad87 100644 --- a/ruby/lib/jam_ruby/models/sale_line_item.rb +++ b/ruby/lib/jam_ruby/models/sale_line_item.rb @@ -4,14 +4,16 @@ module JamRuby JAMBLASTER = 'JamBlaster' JAMCLOUD = 'JamCloud' JAMTRACK = 'JamTrack' + GIFTCARD = 'GiftCardType' belongs_to :sale, class_name: 'JamRuby::Sale' belongs_to :jam_track, class_name: 'JamRuby::JamTrack' belongs_to :jam_track_right, class_name: 'JamRuby::JamTrackRight' + belongs_to :gift_card, class_name: 'JamRuby::GiftCard' belongs_to :affiliate_referral, class_name: 'JamRuby::AffiliatePartner', foreign_key: :affiliate_referral_id has_many :recurly_transactions, class_name: 'JamRuby::RecurlyTransactionWebHook', inverse_of: :sale_line_item, foreign_key: 'subscription_id', primary_key: 'recurly_subscription_uuid' - validates :product_type, inclusion: {in: [JAMBLASTER, JAMCLOUD, JAMTRACK]} + validates :product_type, inclusion: {in: [JAMBLASTER, JAMCLOUD, JAMTRACK, GIFTCARD]} validates :unit_price, numericality: {only_integer: false} validates :quantity, numericality: {only_integer: true} validates :free, numericality: {only_integer: true} @@ -21,9 +23,19 @@ module JamRuby validates :recurly_plan_code, presence:true validates :sale, presence:true + def is_jam_track? + product_type == JAMTRACK + end + + def is_gift_card? + product_type == GIFTCARD + end + def product if product_type == JAMTRACK JamTrack.find_by_id(product_id) + elsif product_type == GIFTCARD + GiftCardType.find_by_id(product_id) else raise 'unsupported product type' end diff --git a/ruby/lib/jam_ruby/models/shopping_cart.rb b/ruby/lib/jam_ruby/models/shopping_cart.rb index 568d94048..fb531c2bd 100644 --- a/ruby/lib/jam_ruby/models/shopping_cart.rb +++ b/ruby/lib/jam_ruby/models/shopping_cart.rb @@ -12,6 +12,8 @@ module JamRuby attr_accessible :quantity, :cart_type, :product_info + attr_accessor :skip_mix_check + validates_uniqueness_of :cart_id, scope: [:cart_type, :user_id, :anonymous_user_id] belongs_to :user, :inverse_of => :shopping_carts, :class_name => "JamRuby::User", :foreign_key => "user_id" @@ -20,12 +22,13 @@ module JamRuby validates :cart_type, presence: true validates :cart_class_name, presence: true validates :marked_for_redeem, numericality: {only_integer: true} + validate :not_mixed default_scope order('created_at DESC') def product_info product = self.cart_product - {name: product.name, price: product.price, product_id: cart_id, plan_code: product.plan_code, real_price: real_price(product), total_price: total_price(product), quantity: quantity, marked_for_redeem: marked_for_redeem, free: free?, sales_region: product.sales_region} unless product.nil? + {type: cart_type, name: product.name, price: product.price, product_id: cart_id, plan_code: product.plan_code, real_price: real_price(product), total_price: total_price(product), quantity: quantity, marked_for_redeem: marked_for_redeem, free: free?, sales_region: product.sales_region, sale_display:product.sale_display} unless product.nil? end # multiply quantity by price @@ -38,6 +41,31 @@ module JamRuby (quantity - marked_for_redeem) * product.price end + def not_mixed + + return if @skip_mix_check + existing_carts = [] + this_user = any_user() + + if this_user + existing_carts = this_user.shopping_carts + end + + existing_carts = existing_carts.to_a + existing_carts << self + + if Sale.is_mixed(existing_carts) + if free? + errors.add(:base, "You can not add a free JamTrack to a cart with non-free items. Please clear out your cart.") + return false + else + errors.add(:base, "You can not add a non-free JamTrack to a cart containing free items. Please clear out your cart.") + return false + end + end + + false + end def cart_product self.cart_class_name.classify.constantize.find_by_id(self.cart_id) unless self.cart_class_name.blank? @@ -51,7 +79,18 @@ module JamRuby marked_for_redeem == quantity end + def any_user + if user + user + elsif anonymous_user_id + AnonymousUser.new(anonymous_user_id, nil) + else + nil + end + end + def self.create user, product, quantity = 1, mark_redeem = false + cart = ShoppingCart.new if user.is_a?(User) cart.user = user @@ -72,39 +111,42 @@ module JamRuby cart_type == JamTrack::PRODUCT_TYPE end + def is_gift_card? + cart_type == GiftCardType::PRODUCT_TYPE + end # returns an array of adjustments for the shopping cart def create_adjustment_attributes(current_user) - raise "not a jam track" unless is_jam_track? + raise "not a jam track or gift card" unless is_jam_track? || is_gift_card? info = self.product_info if free? - # create the credit, then the pseudo charge [ { accounting_code: PURCHASE_FREE_CREDIT, currency: 'USD', unit_amount_in_cents: -(info[:total_price] * 100).to_i, - description: "JamTrack: " + info[:name] + " (Credit)", + description: info[:sale_display] + " (Credit)", tax_exempt: true }, { accounting_code: PURCHASE_FREE, currency: 'USD', unit_amount_in_cents: (info[:total_price] * 100).to_i, - description: "JamTrack: " + info[:name], + description: info[:sale_display], tax_exempt: true } ] else + [ { accounting_code: PURCHASE_NORMAL, currency: 'USD', unit_amount_in_cents: (info[:total_price] * 100).to_i, - description: "JamTrack: " + info[:name], + description: info[:sale_display], tax_exempt: false } ] @@ -113,8 +155,13 @@ module JamRuby def self.move_to_user(user, anonymous_user, shopping_carts) shopping_carts.each do |shopping_cart| - mark_redeem = ShoppingCart.user_has_redeemable_jam_track?(user) - cart = ShoppingCart.create(user, shopping_cart.cart_product, shopping_cart.quantity, mark_redeem) + if shopping_cart.is_jam_track? + mark_redeem = ShoppingCart.user_has_redeemable_jam_track?(user) + cart = ShoppingCart.create(user, shopping_cart.cart_product, shopping_cart.quantity, mark_redeem) + else + cart = ShoppingCart.create(user, shopping_cart.cart_product, shopping_cart.quantity, false) + end + end anonymous_user.destroy_all_shopping_carts @@ -134,28 +181,32 @@ module JamRuby # if no shpping carts have been marked, then mark it redeemable # should be wrapped in a TRANSACTION def self.user_has_redeemable_jam_track?(any_user) - mark_redeem = false - if APP_CONFIG.one_free_jamtrack_per_user && any_user.has_redeemable_jamtrack - mark_redeem = true # start out assuming we can redeem... + + if any_user.has_redeemable_jamtrack || any_user.gifted_jamtracks > 0 + + free_in_cart = 0 any_user.shopping_carts.each do |shopping_cart| # but if we find any shopping cart item already marked for redeem, then back out of mark_redeem=true - if shopping_cart.cart_type == JamTrack::PRODUCT_TYPE && shopping_cart.marked_for_redeem > 0 - mark_redeem = false - break + if shopping_cart.cart_type == JamTrack::PRODUCT_TYPE + free_in_cart += shopping_cart.marked_for_redeem end end + + any_user.free_jamtracks > free_in_cart + else + false end - mark_redeem end # adds a jam_track to cart, checking for promotions - def self.add_jam_track_to_cart(any_user, jam_track) + def self.add_jam_track_to_cart(any_user, jam_track, clear:false) cart = nil ShoppingCart.transaction do - if any_user.has_redeemable_jamtrack - # if you still have a freebie available to you, or if you are an anonymous user, we make sure there is nothing else in your shopping cart - any_user.destroy_all_shopping_carts + if clear + # if you are an anonymous user, we make sure there is nothing else in your shopping cart ... keep it clean for the 'new user rummaging around for a freebie scenario' + any_user.destroy_jam_track_shopping_carts + any_user.reload end mark_redeem = ShoppingCart.user_has_redeemable_jam_track?(any_user) @@ -164,23 +215,66 @@ module JamRuby cart end + def self.add_item_to_cart(any_user, item) + cart = nil + ShoppingCart.transaction do + cart = ShoppingCart.create(any_user, item, 1, false) + end + cart + end + # deletes a jam track from the shopping cart, updating redeem flag as necessary def self.remove_jam_track_from_cart(any_user, cart) ShoppingCart.transaction do cart.destroy - # check if we should move the redemption + + # so that user.shopping_carts reflects truth + any_user.reload + + # check if we should move the redemption around automatically mark_redeem = ShoppingCart.user_has_redeemable_jam_track?(any_user) carts = any_user.shopping_carts - # if we find any carts on the account, mark one redeemable + # if we find any carts on the account that are not redeemed, mark first one redeemable if mark_redeem && carts.length > 0 - carts[0].redeem(mark_redeem) - carts[0].save + carts.each do |cart| + if cart.marked_for_redeem == 0 + if cart.quantity > 1 + raise 'unknown situation for redeemption juggling' + end + cart.redeem(mark_redeem) + cart.save + break + end + end end end end + def self.remove_item_from_cart(any_user, cart) + ShoppingCart.transaction do + cart.destroy + end + end + + # if the number of items in the shopping cart is less than gifted_jamtracks on the user, then fix them all up + def self.apply_gifted_jamtracks(user) + jam_track_carts = user.shopping_carts.where(cart_type:JamTrack::PRODUCT_TYPE) + + if jam_track_carts.count > user.gifted_jamtracks + # just whack everything in their shopping cart + user.destroy_all_shopping_carts + return + end + + jam_track_carts.each do |cart| + cart.skip_mix_check = true + cart.marked_for_redeem = 1 + cart.save! + end + end + def port(user, anonymous_user) ShoppingCart.transaction do diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index a9c46801a..907cd9776 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -40,7 +40,7 @@ module JamRuby attr_accessible :first_name, :last_name, :email, :city, :password, :password_confirmation, :state, :country, :birth_date, :subscribe_email, :terms_of_service, :original_fpfile, :cropped_fpfile, :cropped_large_fpfile, :cropped_s3_path, :cropped_large_s3_path, :photo_url, :large_photo_url, :crop_selection # updating_password corresponds to a lost_password - attr_accessor :updating_password, :updating_email, :updated_email, :update_email_confirmation_url, :administratively_created, :current_password, :setting_password, :confirm_current_password, :updating_avatar, :updating_progression_field, :mods_json + attr_accessor :updating_password, :updating_email, :updated_email, :update_email_confirmation_url, :administratively_created, :current_password, :setting_password, :confirm_current_password, :updating_avatar, :updating_progression_field, :mods_json, :expecting_gift_card belongs_to :icecast_server_group, class_name: "JamRuby::IcecastServerGroup", inverse_of: :users, foreign_key: 'icecast_server_group_id' @@ -148,6 +148,11 @@ module JamRuby # events has_many :event_sessions, :class_name => "JamRuby::EventSession" + # gift cards + has_many :gift_cards, :class_name=> "JamRuby::GiftCard" + has_many :gift_card_purchases, :class_name=> "JamRuby::GiftCardPurchase" + + # affiliate_partner has_one :affiliate_partner, :class_name => "JamRuby::AffiliatePartner", :foreign_key => :partner_user_id, inverse_of: :partner_user belongs_to :affiliate_referral, :class_name => "JamRuby::AffiliatePartner", :foreign_key => :affiliate_referral_id, :counter_cache => :referral_user_count @@ -194,6 +199,7 @@ module JamRuby validates :terms_of_service, :acceptance => {:accept => true, :on => :create, :allow_nil => false } validates :reuse_card, :inclusion => {:in => [true, false]} validates :has_redeemable_jamtrack, :inclusion => {:in => [true, false]} + validates :gifted_jamtracks, presence: true, :numericality => { :less_than_or_equal_to => 100 } validates :subscribe_email, :inclusion => {:in => [nil, true, false]} validates :musician, :inclusion => {:in => [true, false]} validates :show_whats_next, :inclusion => {:in => [nil, true, false]} @@ -214,6 +220,7 @@ module JamRuby validate :email_case_insensitive_uniqueness validate :update_email_case_insensitive_uniqueness, :if => :updating_email validate :validate_mods + validate :presence_gift_card, :if => :expecting_gift_card scope :musicians, where(:musician => true) scope :fans, where(:musician => false) @@ -233,6 +240,18 @@ module JamRuby end end + def has_any_free_jamtracks + has_redeemable_jamtrack || gifted_jamtracks > 0 + end + + def free_jamtracks + (has_redeemable_jamtrack ? 1 : 0) + gifted_jamtracks + end + + def show_free_jamtrack? + ShoppingCart.user_has_redeemable_jam_track?(self) + end + def failed_qualification(reason) self.last_failed_certified_gear_at = DateTime.now self.last_failed_certified_gear_reason = reason @@ -255,6 +274,12 @@ module JamRuby end end + def presence_gift_card + if self.gift_cards.length == 0 + errors.add(:gift_card, ValidationMessages::NOT_FOUND) + end + end + def validate_current_password # checks if the user put in their current password (used when changing your email, for instance) errors.add(:current_password, ValidationMessages::NOT_YOUR_PASSWORD) if should_confirm_existing_password? && !valid_password?(self.current_password) @@ -1025,6 +1050,7 @@ module JamRuby reuse_card = options[:reuse_card] signup_hint = options[:signup_hint] affiliate_partner = options[:affiliate_partner] + gift_card = options[:gift_card] user = User.new @@ -1036,6 +1062,9 @@ module JamRuby user.terms_of_service = terms_of_service user.musician = musician user.reuse_card unless reuse_card.nil? + user.gifted_jamtracks = 0 + user.has_redeemable_jamtrack = true + # FIXME: Setting random password for social network logins. This # is because we have validations all over the place on this. @@ -1140,8 +1169,22 @@ module JamRuby end end + found_gift_card = nil + + # if a gift card value was passed in, then try to find that gift card and apply it to user + if gift_card + user.expecting_gift_card = true + found_gift_card = GiftCard.where(code:gift_card).where(user_id:nil).first + user.gift_cards << found_gift_card if found_gift_card + end + user.save + if found_gift_card + user.reload + ShoppingCart.apply_gifted_jamtracks(user) + end + # if the user has just one, free jamtrack in their shopping cart, and it matches the signup hint, then auto-buy it # only_freebie_in_cart = # signup_hint && @@ -1181,6 +1224,7 @@ module JamRuby end end end + user.reload if user.id# gift card adding gifted_jamtracks doesn't reflect here until reload user end # def signup @@ -1636,6 +1680,11 @@ module JamRuby ShoppingCart.where("user_id=?", self).destroy_all end + def destroy_jam_track_shopping_carts + ShoppingCart.destroy_all(anonymous_user_id: @id, cart_type: JamTrack::PRODUCT_TYPE) + end + + def unsubscribe_token self.class.create_access_token(self) end diff --git a/ruby/lib/jam_ruby/resque/jam_track_mixdown_packager.rb b/ruby/lib/jam_ruby/resque/jam_track_mixdown_packager.rb index c954e62d3..191e41d33 100644 --- a/ruby/lib/jam_ruby/resque/jam_track_mixdown_packager.rb +++ b/ruby/lib/jam_ruby/resque/jam_track_mixdown_packager.rb @@ -10,9 +10,11 @@ module JamRuby include JamRuby::S3ManagerMixin + TAP_IN_PADDING = 2 MAX_PAN = 90 MIN_PAN = -90 + KNOCK_SECONDS = 0.035 attr_accessor :mixdown_package_id, :settings, :mixdown_package, :mixdown, :step @queue = :jam_track_mixdown_packager @@ -55,6 +57,8 @@ module JamRuby @mixdown = @mixdown_package.jam_track_mixdown @settings = JSON.parse(@mixdown.settings) + process_jmep + track_settings # compute the step count @@ -102,6 +106,236 @@ module JamRuby vol != 1.0 || pan != 0 end + def process_jmep + @start_points = [] + @initial_padding = 0.0 + + speed = @settings['speed'] || 0 + + @speed_factor = 1.0 + (-speed.to_f / 100.0) + @inverse_speed_factor = 1 - (-speed.to_f / 100) + + log.info("speed factor #{@speed_factor}") + + jmep = @mixdown.jam_track.jmep_json + if jmep + jmep = JSON.parse(jmep) + end + + if jmep.nil? + log.debug("no jmep") + return + end + + events = jmep["Events"] + + return if events.nil? || events.length == 0 + + metronome = nil + events.each do |event| + if event.has_key?("metronome") + metronome = event["metronome"] + break + end + end + + if metronome.nil? || metronome.length == 0 + log.debug("no metronome events for jmep", jmep) + return + end + + @start_points = metronome.select { |x| puts x.inspect; x["action"] == "start" } + + log.debug("found #{@start_points.length} metronome start points") + + start_point = @start_points[0] + + if start_point + start_time = parse_time(start_point["ts"]) + + if start_time < 2.0 + padding = start_time - 2.0 + @initial_padding = padding.abs + @initial_tap_in = start_time + end + end + + if @speed_factor != 1.0 + metronome.length.times do |count| + + # we expect to find metronome start/stop grouped + if count % 2 == 0 + + start = metronome[count] + stop = metronome[count + 1] + + if start["action"] != "start" || stop["action"] != "stop" + # bail out + log.error("found de-coupled metronome events #{start.to_json} | #{stop.to_json}") + next + end + + bpm = start["bpm"].to_f + stop_time = parse_time(stop['ts']) + ticks = stop['ticks'].to_i + + + new_bpm = bpm * @inverse_speed_factor + new_stop_time = stop_time * @speed_factor + new_start_time = new_stop_time - (60.0/new_bpm * ticks) + + log.info("original bpm:#{bpm} start: #{parse_time(start["ts"])} stop: #{stop_time}") + log.info("updated bpm:#{new_bpm} start: #{new_start_time} stop: #{new_stop_time}") + + stop["ts"] = new_stop_time + start["ts"] = new_start_time + start["bpm"] = new_bpm + stop["bpm"] = new_bpm + + @tap_in_initial_silence = (@initial_tap_in + @initial_padding) * @speed_factor + + end + + end + end + + @start_points = metronome.select { |x| puts x.inspect; x["action"] == "start" } + + end + + # format like: "-0:00:02:820" + def parse_time(ts) + + if ts.is_a?(Float) + return ts + end + + time = 0.0 + negative = false + + if ts.start_with?('-') + negative = true + end + + # parse time_format + bits = ts.split(':').reverse + + bit_position = 0 + bits.each do |bit| + if bit_position == 0 + # milliseconds + milliseconds = bit.to_f + time += milliseconds/1000 + elsif bit_position == 1 + # seconds + time += bit.to_f + elsif bit_position == 2 + # minutes + time += 60 * bit.to_f + elsif bit_position == 3 + # hours + # not bothering + end + + bit_position += 1 + end + + if negative + time = 0.0 - time + end + + time + end + + def path_to_resources + File.join(File.dirname(File.expand_path(__FILE__)), '../../../lib/jam_ruby/app/assets/sounds') + end + + def knock_file + if long_sample_rate == 44100 + knock = File.join(path_to_resources, 'knock44.wav') + else + knock = File.join(path_to_resources, 'knock48.wav') + end + + log.debug("knock file path: " + knock) + knock + end + + def create_silence(tmp_dir, segment_count, duration) + file = File.join(tmp_dir, "#{segment_count}.wav") + + # -c 2 means stereo + cmd("sox -n -r #{long_sample_rate} -c 2 #{file} trim 0.0 #{duration}", "silence") + + file + end + + def create_tapin_track(tmp_dir) + + return nil if @start_points.length == 0 + + segment_count = 0 + + + #initial_silence = @initial_tap_in + @initial_padding + + initial_silence = @tap_in_initial_silence + + #log.info("tapin data: initial_tap_in: #{@initial_tap_in}, initial_padding: #{@initial_padding}, initial_silence: #{initial_silence}") + + time_points = [] + files = [] + if initial_silence > 0 + + files << create_silence(tmp_dir, segment_count, initial_silence) + + time_points << {type: :silence, ts: initial_silence} + segment_count += 1 + end + + + time_cursor = nil + @start_points.each do |start_point| + tap_time = parse_time(start_point["ts"]) + if !time_cursor.nil? + between_silence = tap_time - time_cursor + files << create_silence(tmp_dir, segment_count, between_silence) + time_points << {type: :silence, ts: between_silence} + end + time_cursor = tap_time + bpm = start_point["bpm"].to_f + + tick_silence = 60.0/bpm - KNOCK_SECONDS + + ticks = start_point["ticks"].to_i + + ticks.times do |tick| + files << knock_file + files << create_silence(tmp_dir, segment_count, tick_silence) + time_points << {type: :knock, ts: KNOCK_SECONDS} + time_points << {type: :silence, ts: tick_silence} + time_cursor + 60.0/bpm + segment_count += 1 + end + end + + log.info("time points for tap-in: #{time_points.inspect}") + # do we need to pad with time? not sure + + sequence_cmd = "sox " + files.each do |file| + sequence_cmd << "\"#{file}\" " + end + + count_in = File.join(tmp_dir, "count-in.wav") + sequence_cmd << "\"#{count_in}\"" + + cmd(sequence_cmd, "count_in") + @count_in_file = count_in + count_in + end + # creates a list of tracks to actually mix def track_settings altered_tracks = @settings["tracks"] || [] @@ -113,6 +347,15 @@ module JamRuby stems = @mixdown.jam_track.stem_tracks @track_count = stems.length + @include_count_in = @settings["count-in"] && @start_points.length > 0 && @mixdown_package.encrypt_type.nil? + + # temp + # @include_count_in = true + + if @include_count_in + @track_count += 1 + end + stems.each do |stem| vol = 1.0 @@ -139,10 +382,14 @@ module JamRuby # if we didn't deliberately skip this one, and if there was no 'match' (meaning user did not specify), then we leave this in unchanged if !skipped && !match - @track_settings << {stem:stem, vol:vol, pan:pan} + @track_settings << {stem: stem, vol: vol, pan: pan} end end + if @include_count_in + @track_settings << {count_in: true, vol: 1.0, pan: 0} + end + @track_settings end @@ -153,14 +400,14 @@ module JamRuby # k = f(i) = (i)/(2*MAX_PAN) + 0.5 # so f(MIN_PAN) = -0.5 + 0.5 = 0 - k = ((pan * (1.0))/ (2.0 * MAX_PAN )) + 0.5 + k = ((pan * (1.0))/ (2.0 * MAX_PAN)) + 0.5 l, r = 0 if k == 0 l = 0.0 r = 1.0 else - l = Math.sqrt(k) + l = Math.sqrt(k) r = Math.sqrt(1-k) end @@ -169,24 +416,26 @@ module JamRuby def package - puts @settings.inspect - puts @track_count - puts @track_settings - puts @track_settings.count + log.info("Settings: #{@settings.to_json}") Dir.mktmpdir do |tmp_dir| # download all files @track_settings.each do |track| - jam_track_track = track[:stem] - file = File.join(tmp_dir, jam_track_track.id + '.ogg') + if track[:count_in] + file = create_tapin_track(tmp_dir) + bump_step(@mixdown_package) + else + jam_track_track = track[:stem] - bump_step(@mixdown_package) + file = File.join(tmp_dir, jam_track_track.id + '.ogg') - # download each track needed - s3_manager.download(jam_track_track.url_by_sample_rate(@mixdown_package.sample_rate), file) + bump_step(@mixdown_package) + # download each track needed + s3_manager.download(jam_track_track.url_by_sample_rate(@mixdown_package.sample_rate), file) + end track[:file] = file end @@ -206,6 +455,8 @@ module JamRuby apply_vol_and_pan tmp_dir + create_silence_padding tmp_dir + mix tmp_dir pitch_speed tmp_dir @@ -218,6 +469,7 @@ module JamRuby @track_settings.each do |track| jam_track_track = track[:stem] + count_in = track[:count_in] file = track[:file] unless should_alter_volume? track @@ -235,7 +487,11 @@ module JamRuby # sox claps.wav claps-remixed.wav remix 1v1.0 2v1.0 - volumed_file = File.join(tmp_dir, jam_track_track.id + '-volumed.ogg') + if count_in + volumed_file = File.join(tmp_dir, 'count-in' + '-volumed.ogg') + else + volumed_file = File.join(tmp_dir, jam_track_track.id + '-volumed.ogg') + end cmd("sox \"#{file}\" \"#{volumed_file}\" remix 1v#{channel_r} 2v#{channel_l}", 'vol_pan') @@ -244,6 +500,29 @@ module JamRuby end end + def create_silence_padding(tmp_dir) + if @initial_padding > 0 && @include_count_in + + @padding_file = File.join(tmp_dir, "initial_padding.ogg") + + # -c 2 means stereo + cmd("sox -n -r #{long_sample_rate} -c 2 #{@padding_file} trim 0.0 #{@initial_padding}", "initial_padding") + + @track_settings.each do |track| + + next if track[:count_in] + + input = track[:volumed_file] + output = input[0..-5] + '-padded.ogg' + + padd_cmd = "sox '#{@padding_file}' '#{input}' '#{output}'" + + cmd(padd_cmd, "pad_track_with_silence") + track[:volumed_file] = output + end + end + end + # output is @mix_file def mix(tmp_dir) @@ -251,6 +530,11 @@ module JamRuby @mix_file = File.join(tmp_dir, "mix.ogg") + + pitch = @settings['pitch'] || 0 + speed = @settings['speed'] || 0 + + # if there is only one track to mix, we need to skip mixing (sox will barf if you try to mix one file), but still divide by number of tracks if @track_settings.count == 1 mix_divide = 1.0/@track_count @@ -263,6 +547,11 @@ module JamRuby cmd = "sox -m" mix_divide = 1.0/@track_count @track_settings.each do |track| + + # if pitch/shifted, we lay the tap-in after pitch/speed shift + # next if (pitch != 0 || speed != 0) && track[:count_in] + next if track[:count_in] + volumed_file = track[:volumed_file] cmd << " -v #{mix_divide} \"#{volumed_file}\"" end @@ -275,6 +564,13 @@ module JamRuby end + def long_sample_rate + sample_rate = 48000 + if @mixdown_package.sample_rate != 48 + sample_rate = 44100 + end + sample_rate + end # output is @speed_mix_file def pitch_speed tmp_dir @@ -300,17 +596,21 @@ module JamRuby # usage: sbsms infile<.wav|.aif|.mp3|.ogg> outfile<.ogg> rate[0.01:100] halfsteps[-48:48] outSampleRateInHz - sample_rate = 48000 - if @mixdown_package.sample_rate != 48 - sample_rate = 44100 - end + sample_rate = long_sample_rate # rate comes in as a percent (like 5, -5 for 5%, -5%). We need to change that to 1.05/ sbsms_speed = speed/100.0 sbsms_speed = 1.0 + sbsms_speed sbsms_pitch = pitch - cmd( "sbsms \"#{@mix_file}\" \"#{@speed_mix_file}\" #{sbsms_speed} #{sbsms_pitch} #{sample_rate}", 'speed_pitch_shift') + cmd("sbsms \"#{@mix_file}\" \"#{@speed_mix_file}\" #{sbsms_speed} #{sbsms_pitch} #{sample_rate}", 'speed_pitch_shift') + end + + if @include_count_in + # lay the tap-ins over the recording + layered = File.join(tmp_dir, "layered_speed_mix.ogg") + cmd("sox -m '#{@count_in_file}' '#{@speed_mix_file}' '#{layered}'", "layer_tap_in") + @speed_mix_file = layered end end @@ -337,7 +637,7 @@ module JamRuby length = File.size(output) computed_md5 = Digest::MD5.new - File.open(output, 'rb').each {|line| computed_md5.update(line)} + File.open(output, 'rb').each { |line| computed_md5.update(line) } md5 = computed_md5.to_s @mixdown_package.finish_sign(s3_url, private_key, length, md5.to_s) @@ -399,7 +699,7 @@ module JamRuby end private_key_file = File.join(tmp_dir, 'skey.pem') - File.open(private_key_file, 'w') {|f| f.write(private_key) } + File.open(private_key_file, 'w') { |f| f.write(private_key) } log.debug("PRIVATE KEY") log.debug(private_key) diff --git a/ruby/spec/factories.rb b/ruby/spec/factories.rb index c8c91241e..024ecdc20 100644 --- a/ruby/spec/factories.rb +++ b/ruby/spec/factories.rb @@ -19,6 +19,8 @@ FactoryGirl.define do terms_of_service true last_jam_audio_latency 5 reuse_card true + has_redeemable_jamtrack true + gifted_jamtracks 0 #u.association :musician_instrument, factory: :musician_instrument, user: u @@ -858,4 +860,18 @@ FactoryGirl.define do legalese Faker::Lorem.paragraphs(6).join("\n\n") end + factory :gift_card, class: 'JamRuby::GiftCard' do + sequence(:code) {n.to_s} + card_type JamRuby::GiftCardType::JAM_TRACKS_5 + end + + factory :gift_card_type, class: 'JamRuby::GiftCardType' do + card_type JamRuby::GiftCardType::JAM_TRACKS_5 + end + + factory :gift_card_purchase, class: 'JamRuby::GiftCardPurchase' do + + association :user, factory: :user + end + end diff --git a/ruby/spec/jam_ruby/models/jam_track_spec.rb b/ruby/spec/jam_ruby/models/jam_track_spec.rb index 814a4cbb3..4ed8e7b28 100644 --- a/ruby/spec/jam_ruby/models/jam_track_spec.rb +++ b/ruby/spec/jam_ruby/models/jam_track_spec.rb @@ -234,6 +234,17 @@ describe JamTrack do query.size.should == 2 end + + it "deals with aggregration (regression)" do + + query, pager, count = JamTrack.index({sort_by: 'jamtrack', artist: 'K.C. And The Sunshine Band'}, user) + count.should == 0 + + jam_track1 = FactoryGirl.create(:jam_track_with_tracks, name: 'Take a Chance On Me', original_artist: 'K.C. And The Sunshine Band') + + query, pager, count = JamTrack.index({sort_by: 'jamtrack', artist: 'K.C. And The Sunshine Band'}, user) + count.should == 1 + end end describe "validations" do diff --git a/ruby/spec/jam_ruby/models/recurly_transaction_web_hook_spec.rb b/ruby/spec/jam_ruby/models/recurly_transaction_web_hook_spec.rb index ba89aee20..bce945d20 100644 --- a/ruby/spec/jam_ruby/models/recurly_transaction_web_hook_spec.rb +++ b/ruby/spec/jam_ruby/models/recurly_transaction_web_hook_spec.rb @@ -135,7 +135,7 @@ describe RecurlyTransactionWebHook do RecurlyTransactionWebHook.create_from_xml(document) - JamTrackRight.find_by_id(jam_track_right.id).should be_nil + JamTrackRight.find_by_id(jam_track_right.id).should_not be_nil end it "deletes jam_track_right when voided" do @@ -154,7 +154,7 @@ describe RecurlyTransactionWebHook do RecurlyTransactionWebHook.create_from_xml(document) - JamTrackRight.find_by_id(jam_track_right.id).should be_nil + JamTrackRight.find_by_id(jam_track_right.id).should_not be_nil end end diff --git a/ruby/spec/jam_ruby/models/sale_line_item_spec.rb b/ruby/spec/jam_ruby/models/sale_line_item_spec.rb index 334166734..4d0340259 100644 --- a/ruby/spec/jam_ruby/models/sale_line_item_spec.rb +++ b/ruby/spec/jam_ruby/models/sale_line_item_spec.rb @@ -6,6 +6,7 @@ describe SaleLineItem do let(:user) {FactoryGirl.create(:user)} let(:user2) {FactoryGirl.create(:user)} let(:jam_track) {FactoryGirl.create(:jam_track)} + let(:gift_card) {FactoryGirl.create(:gift_card_type, card_type: GiftCardType::JAM_TRACKS_10)} describe "associations" do @@ -23,7 +24,7 @@ describe SaleLineItem do describe "state" do - it "success" do + it "jam track success" do sale = Sale.create_jam_track_sale(user) shopping_cart = ShoppingCart.create(user, jam_track) sale_line_item = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, 'some_recurly_uuid', nil, nil) @@ -37,5 +38,20 @@ describe SaleLineItem do success: true }) end + + it "gift card success" do + sale = Sale.create_jam_track_sale(user) + shopping_cart = ShoppingCart.create(user, gift_card) + sale_line_item = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, 'some_recurly_uuid', nil, nil) + transaction = FactoryGirl.create(:recurly_transaction_web_hook, subscription_id: 'some_recurly_uuid') + + sale_line_item.reload + sale_line_item.state.should eq({ + void: false, + refund: false, + fail: false, + success: true + }) + end end end diff --git a/ruby/spec/jam_ruby/models/sale_spec.rb b/ruby/spec/jam_ruby/models/sale_spec.rb index 685d8dfbd..fc89145cb 100644 --- a/ruby/spec/jam_ruby/models/sale_spec.rb +++ b/ruby/spec/jam_ruby/models/sale_spec.rb @@ -5,6 +5,24 @@ describe Sale do let(:user) {FactoryGirl.create(:user)} let(:user2) {FactoryGirl.create(:user)} let(:jam_track) {FactoryGirl.create(:jam_track)} + let(:jam_track2) {FactoryGirl.create(:jam_track)} + let(:jam_track3) {FactoryGirl.create(:jam_track)} + let(:gift_card) {GiftCardType.jam_track_5} + + def assert_free_line_item(sale_line_item, jamtrack) + sale_line_item.recurly_tax_in_cents.should be_nil + sale_line_item.recurly_total_in_cents.should be_nil + sale_line_item.recurly_currency.should be_nil + sale_line_item.recurly_discount_in_cents.should be_nil + sale_line_item.product_type.should eq(JamTrack::PRODUCT_TYPE) + sale_line_item.unit_price.should eq(jamtrack.price) + sale_line_item.quantity.should eq(1) + sale_line_item.free.should eq(1) + sale_line_item.sales_tax.should be_nil + sale_line_item.shipping_handling.should eq(0) + sale_line_item.recurly_plan_code.should eq(jamtrack.plan_code) + sale_line_item.product_id.should eq(jamtrack.id) + end describe "index" do it "empty" do @@ -47,7 +65,11 @@ describe Sale do let(:user) {FactoryGirl.create(:user)} let(:jamtrack) { FactoryGirl.create(:jam_track) } + let(:jamtrack2) { FactoryGirl.create(:jam_track) } + let(:jamtrack3) { FactoryGirl.create(:jam_track) } + let(:jamtrack4) { FactoryGirl.create(:jam_track) } let(:jam_track_price_in_cents) { (jamtrack.price * 100).to_i } + let(:gift_card_price_in_cents) { (gift_card.price * 100).to_i } let(:client) { RecurlyClient.new } let(:billing_info) { info = {} @@ -75,6 +97,77 @@ describe Sale do end end + it "for a gift card" do + shopping_cart = ShoppingCart.create user, gift_card, 1, false + client.find_or_create_account(user, billing_info) + + sales = Sale.place_order(user, [shopping_cart]) + + user.reload + user.sales.length.should eq(1) + + sales.should eq(user.sales) + sale = sales[0] + sale.recurly_invoice_id.should_not be_nil + + sale.recurly_subtotal_in_cents.should eq(gift_card_price_in_cents) + sale.recurly_tax_in_cents.should eq(0) + sale.recurly_total_in_cents.should eq(gift_card_price_in_cents) + sale.recurly_currency.should eq('USD') + + sale.order_total.should eq(gift_card.price) + sale.sale_line_items.length.should == 1 + sale_line_item = sale.sale_line_items[0] + # validate we are storing pricing info from recurly + sale_line_item.recurly_tax_in_cents.should eq(0) + sale_line_item.recurly_total_in_cents.should eq(gift_card_price_in_cents) + sale_line_item.recurly_currency.should eq('USD') + sale_line_item.recurly_discount_in_cents.should eq(0) + sale_line_item.product_type.should eq(GiftCardType::PRODUCT_TYPE) + sale_line_item.unit_price.should eq(gift_card.price) + sale_line_item.quantity.should eq(1) + sale_line_item.free.should eq(0) + sale_line_item.sales_tax.should be_nil + sale_line_item.shipping_handling.should eq(0) + sale_line_item.recurly_plan_code.should eq(gift_card.plan_code) + sale_line_item.product_id.should eq(gift_card.id) + sale_line_item.recurly_subscription_uuid.should be_nil + sale_line_item.recurly_adjustment_uuid.should_not be_nil + sale_line_item.recurly_adjustment_credit_uuid.should be_nil + sale_line_item.recurly_adjustment_uuid.should_not be_nil + + # verify subscription is in Recurly + recurly_account = client.get_account(user) + adjustments = recurly_account.adjustments + adjustments.should_not be_nil + adjustments.should have(1).items + purchase= adjustments[0] + purchase.unit_amount_in_cents.should eq((gift_card.price * 100).to_i) + purchase.accounting_code.should eq(ShoppingCart::PURCHASE_NORMAL) + purchase.description.should eq("JamTracks Gift Card (5)") + purchase.state.should eq('invoiced') + purchase.uuid.should eq(sale_line_item.recurly_adjustment_uuid) + + invoices = recurly_account.invoices + invoices.should have(1).items + invoice = invoices[0] + invoice.uuid.should eq(sale.recurly_invoice_id) + invoice.line_items.should have(1).items # should have single adjustment associated + invoice.line_items[0].should eq(purchase) + invoice.subtotal_in_cents.should eq((gift_card.price * 100).to_i) + invoice.total_in_cents.should eq((gift_card.price * 100).to_i) + invoice.state.should eq('collected') + + # verify jam_track_rights data + user.gift_card_purchases.should_not be_nil + user.gift_card_purchases.should have(1).items + user.gift_card_purchases.last.gift_card_type.should eq(GiftCardType.jam_track_5) + user.has_redeemable_jamtrack.should be_true + + sale_line_item.affiliate_referral.should be_nil + sale_line_item.affiliate_referral_fee_in_cents.should be_nil + end + it "for a free jam track" do shopping_cart = ShoppingCart.create user, jamtrack, 1, true @@ -87,6 +180,7 @@ describe Sale do sales.should eq(user.sales) sale = sales[0] + sale.recurly_invoice_id.should be_nil sale.recurly_subtotal_in_cents.should eq(0) @@ -132,6 +226,69 @@ describe Sale do user.has_redeemable_jamtrack.should be_false end + it "for two jam tracks (1 freebie, 1 gifted), then 1 gifted/1 pay" do + user.gifted_jamtracks = 2 + user.save! + + shopping_cart1 = ShoppingCart.create user, jamtrack, 1, true + shopping_cart2 = ShoppingCart.create user, jamtrack2, 1, true + + client.find_or_create_account(user, billing_info) + + sales = Sale.place_order(user, [shopping_cart1, shopping_cart2]) + + user.reload + user.sales.length.should eq(1) + sale = sales[0] + sale.reload + + sale.recurly_invoice_id.should be_nil + + sale.recurly_subtotal_in_cents.should eq(0) + sale.recurly_tax_in_cents.should eq(0) + sale.recurly_total_in_cents.should eq(0) + sale.recurly_currency.should eq('USD') + sale.order_total.should eq(0) + sale.sale_line_items.length.should == 2 + + assert_free_line_item(sale.sale_line_items[0], jamtrack) + assert_free_line_item(sale.sale_line_items[1], jamtrack2) + + # verify jam_track_rights data + right1 = JamTrackRight.where(user_id: user.id).where(jam_track_id: jamtrack.id).first + right2 = JamTrackRight.where(user_id: user.id).where(jam_track_id: jamtrack2.id).first + user.jam_track_rights.should have(2).items + + right1.redeemed.should be_true + right2.redeemed.should be_true + user.has_redeemable_jamtrack.should be_false + user.gifted_jamtracks.should eq(1) + + + + # OK! Now make a second purchase; this time, buy one free, one not free + shopping_cart3 = ShoppingCart.create user, jamtrack3, 1, true + + client.find_or_create_account(user, billing_info) + + sales = Sale.place_order(user, [shopping_cart3]) + + user.reload + user.sales.length.should eq(2) + sale = sales[0] + sale.reload + + sale.recurly_invoice_id.should be_nil + sale.recurly_subtotal_in_cents.should eq(0) + sale.recurly_tax_in_cents.should eq(0) + sale.recurly_total_in_cents.should eq(0) + sale.recurly_currency.should eq('USD') + sale.order_total.should eq(0) + sale.sale_line_items.length.should == 1 + + assert_free_line_item(sale.sale_line_items[0], jamtrack3) + end + it "for a free jam track with an affiliate association" do partner = FactoryGirl.create(:affiliate_partner) user.affiliate_referral = partner diff --git a/ruby/spec/jam_ruby/models/shopping_cart_spec.rb b/ruby/spec/jam_ruby/models/shopping_cart_spec.rb index 6be02e8d4..8d3b724b1 100644 --- a/ruby/spec/jam_ruby/models/shopping_cart_spec.rb +++ b/ruby/spec/jam_ruby/models/shopping_cart_spec.rb @@ -3,8 +3,15 @@ require 'spec_helper' describe ShoppingCart do let(:user) { FactoryGirl.create(:user) } - let(:jam_track) {FactoryGirl.create(:jam_track) } - let(:jam_track2) {FactoryGirl.create(:jam_track) } + let(:jam_track) { FactoryGirl.create(:jam_track) } + let(:jam_track2) { FactoryGirl.create(:jam_track) } + let(:jam_track3) { FactoryGirl.create(:jam_track) } + let(:jam_track4) { FactoryGirl.create(:jam_track) } + let(:jam_track5) { FactoryGirl.create(:jam_track) } + let(:jam_track6) { FactoryGirl.create(:jam_track) } + let(:jam_track7) { FactoryGirl.create(:jam_track) } + let(:gift_card) {FactoryGirl.create(:gift_card_type)} + let(:gift_card2) {FactoryGirl.create(:gift_card_type)} before(:each) do ShoppingCart.delete_all @@ -13,6 +20,9 @@ describe ShoppingCart do it "can reference a shopping cart" do shopping_cart = ShoppingCart.create user, jam_track, 1 + shopping_cart.errors.any?.should be_false + shopping_cart.valid?.should be_true + user.reload ShoppingCart.count.should == 1 user.shopping_carts.count.should == 1 user.shopping_carts[0].product_info[:name].should == jam_track.name @@ -21,18 +31,21 @@ describe ShoppingCart do user.shopping_carts[0].quantity.should == 1 end - - it "maintains only one fre JamTrack in ShoppingCart" do - cart1 = ShoppingCart.add_jam_track_to_cart(user, jam_track) + it "maintains only one free JamTrack in ShoppingCart" do + cart1 = ShoppingCart.add_jam_track_to_cart(user, jam_track, clear: true) cart1.should_not be_nil cart1.errors.any?.should be_false user.reload - cart2 = ShoppingCart.add_jam_track_to_cart(user, jam_track) + cart2 = ShoppingCart.add_jam_track_to_cart(user, jam_track, clear: true) cart2.errors.any?.should be_false user.reload user.shopping_carts.length.should eq(1) - cart3 = ShoppingCart.add_jam_track_to_cart(user, jam_track2) - cart3.errors.any?.should be_false + cart3 = ShoppingCart.add_item_to_cart(user, gift_card) + cart3.errors.any?.should be_true + user.reload + user.shopping_carts.length.should eq(1) + cart4 = ShoppingCart.add_jam_track_to_cart(user, jam_track2, clear: true) + cart4.errors.any?.should be_false user.reload user.shopping_carts.length.should eq(1) end @@ -48,24 +61,159 @@ describe ShoppingCart do cart2.errors.any?.should be_true end + it "a second giftcard just adds quantity" do + + end + describe "redeemable behavior" do it "removes redeemable item to shopping cart (maintains only one in cart)" do user.has_redeemable_jamtrack.should be_true + cart1 = ShoppingCart.add_jam_track_to_cart(user, jam_track, clear: true) + cart1.should_not be_nil + cart1.errors.any?.should be_false + cart1.marked_for_redeem.should eq(1) + user.reload + cart2 = ShoppingCart.add_jam_track_to_cart(user, jam_track2, clear: true) + cart2.should_not be_nil + cart2.errors.any?.should be_false + cart2.marked_for_redeem.should eq(1) + + ShoppingCart.find_by_id(cart1.id).should be nil + + + ShoppingCart.remove_jam_track_from_cart(user, cart2) + + user.reload + user.shopping_carts.length.should eq(0) + ShoppingCart.find_by_id(cart2.id).should be nil + end + end + + describe "multiple free jamtracks" do + + before(:each) do + user.gifted_jamtracks = 5 + user.save! + end + + it "user can add and remove jamtracks without issue, until 'mixed' free/non-free is hit" do cart1 = ShoppingCart.add_jam_track_to_cart(user, jam_track) cart1.should_not be_nil + cart1.errors.any?.should be_false + user.reload cart2 = ShoppingCart.add_jam_track_to_cart(user, jam_track2) - cart2.should_not be_nil - + cart2.errors.any?.should be_false + user.reload + user.shopping_carts.length.should eq(2) cart1.marked_for_redeem.should eq(1) cart2.marked_for_redeem.should eq(1) - ShoppingCart.remove_jam_track_from_cart(user, jam_track) - user.shopping_carts.length.should eq(0) - cart2.reload + cart3 = ShoppingCart.add_jam_track_to_cart(user, jam_track3) + cart3.errors.any?.should be_false + user.reload + user.shopping_carts.length.should eq(3) + cart1.marked_for_redeem.should eq(1) cart2.marked_for_redeem.should eq(1) + cart3.marked_for_redeem.should eq(1) + + cart4 = ShoppingCart.add_jam_track_to_cart(user, jam_track4) + cart4.errors.any?.should be_false + user.reload + user.shopping_carts.length.should eq(4) + cart1.marked_for_redeem.should eq(1) + cart2.marked_for_redeem.should eq(1) + cart3.marked_for_redeem.should eq(1) + cart4.marked_for_redeem.should eq(1) + + cart5 = ShoppingCart.add_jam_track_to_cart(user, jam_track5) + cart5.errors.any?.should be_false + user.reload + user.shopping_carts.length.should eq(5) + cart1.marked_for_redeem.should eq(1) + cart2.marked_for_redeem.should eq(1) + cart3.marked_for_redeem.should eq(1) + cart4.marked_for_redeem.should eq(1) + cart5.marked_for_redeem.should eq(1) + + cart6 = ShoppingCart.add_jam_track_to_cart(user, jam_track6) + cart6.errors.any?.should be_false + user.reload + user.shopping_carts.length.should eq(6) + cart1.marked_for_redeem.should eq(1) + cart2.marked_for_redeem.should eq(1) + cart3.marked_for_redeem.should eq(1) + cart4.marked_for_redeem.should eq(1) + cart5.marked_for_redeem.should eq(1) + cart6.marked_for_redeem.should eq(1) + + cart7 = ShoppingCart.add_jam_track_to_cart(user, jam_track7) + cart7.errors.any?.should be_true + user.reload + user.shopping_carts.length.should eq(6) + cart1.marked_for_redeem.should eq(1) + cart2.marked_for_redeem.should eq(1) + cart3.marked_for_redeem.should eq(1) + cart4.marked_for_redeem.should eq(1) + cart5.marked_for_redeem.should eq(1) + cart6.marked_for_redeem.should eq(1) + end + end + + describe "gift cards" do + it "can not add multiple of same type" do + cart1 = ShoppingCart.add_item_to_cart(user, gift_card) + cart1.should_not be_nil + cart1.errors.any?.should be_false + + user.reload + user.has_redeemable_jamtrack = true + user.shopping_carts.length.should eq(1) + user.shopping_carts[0].quantity.should eql(1) + + cart2 = ShoppingCart.add_item_to_cart(user, gift_card) + cart2.should_not be_nil + # it's the same type, so it's blocked + cart2.errors.any?.should be_true + cart2.errors[:cart_id].should eq(["has already been taken"]) + end + end + + describe "mixed" do + it "non-free then free" do + # you shouldn't be able to add a free after a non-free + user.has_redeemable_jamtrack = false + user.save! + + cart1 = ShoppingCart.add_jam_track_to_cart(user, jam_track) + cart1.should_not be_nil + cart1.errors.any?.should be_false + + user.has_redeemable_jamtrack = true + user.save! + user.reload + cart2 = ShoppingCart.add_jam_track_to_cart(user, jam_track2) + cart2.errors.any?.should be_true + cart2.errors[:base].should eq(["You can not add a free JamTrack to a cart with non-free items. Please clear out your cart."]) + + user.shopping_carts.length.should eq(1) + end + + it "free then non-free" do + + cart1 = ShoppingCart.add_jam_track_to_cart(user, jam_track) + cart1.should_not be_nil + cart1.errors.any?.should be_false + + user.reload + + cart2 = ShoppingCart.add_jam_track_to_cart(user, jam_track2) + cart2.errors.any?.should be_true + cart2.errors[:base].should eq(["You can not add a non-free JamTrack to a cart containing free items. Please clear out your cart."]) + + user.shopping_carts.length.should eq(1) end end end diff --git a/ruby/spec/spec_helper.rb b/ruby/spec/spec_helper.rb index 93ec5dda7..7f189e159 100644 --- a/ruby/spec/spec_helper.rb +++ b/ruby/spec/spec_helper.rb @@ -95,13 +95,13 @@ end config.before(:suite) do DatabaseCleaner.strategy = :transaction - DatabaseCleaner.clean_with(:deletion, {pre_count: true, reset_ids:false, :except => %w[instruments genres icecast_server_groups jamcompany jamisp geoipblocks geoipisp geoiplocations cities regions countries generic_state spatial_ref_sys] }) + DatabaseCleaner.clean_with(:deletion, {pre_count: true, reset_ids:false, :except => %w[gift_card_types instruments genres icecast_server_groups jamcompany jamisp geoipblocks geoipisp geoiplocations cities regions countries generic_state spatial_ref_sys] }) end config.around(:each) do |example| # set no_transaction: true as metadata on your test to use deletion strategy instead if example.metadata[:no_transaction] - DatabaseCleaner.strategy = :deletion, {pre_count: true, reset_ids:false, :except => %w[instruments genres icecast_server_groups jamcompany jamisp geoipblocks geoipisp geoiplocations cities regions countries generic_state spatial_ref_sys] } + DatabaseCleaner.strategy = :deletion, {pre_count: true, reset_ids:false, :except => %w[gift_card_types instruments genres icecast_server_groups jamcompany jamisp geoipblocks geoipisp geoiplocations cities regions countries generic_state spatial_ref_sys] } else DatabaseCleaner.strategy = :transaction end diff --git a/web/app/assets/images/landing/gift_card.png b/web/app/assets/images/landing/gift_card.png new file mode 100644 index 000000000..e652e81db Binary files /dev/null and b/web/app/assets/images/landing/gift_card.png differ diff --git a/web/app/assets/images/web/button_cta_jamblaster.png b/web/app/assets/images/web/button_cta_jamblaster.png index 8650fe322..aed1fab4a 100644 Binary files a/web/app/assets/images/web/button_cta_jamblaster.png and b/web/app/assets/images/web/button_cta_jamblaster.png differ diff --git a/web/app/assets/images/web/button_cta_jamtrack.png b/web/app/assets/images/web/button_cta_jamtrack.png index 998d89b75..d8dd6e40c 100644 Binary files a/web/app/assets/images/web/button_cta_jamtrack.png and b/web/app/assets/images/web/button_cta_jamtrack.png differ diff --git a/web/app/assets/images/web/button_cta_platform.png b/web/app/assets/images/web/button_cta_platform.png index dba8d499b..a2ddf74ef 100644 Binary files a/web/app/assets/images/web/button_cta_platform.png and b/web/app/assets/images/web/button_cta_platform.png differ diff --git a/web/app/assets/javascripts/accounts_profile_avatar.js b/web/app/assets/javascripts/accounts_profile_avatar.js index 63d1aca1f..2ab8858cd 100644 --- a/web/app/assets/javascripts/accounts_profile_avatar.js +++ b/web/app/assets/javascripts/accounts_profile_avatar.js @@ -40,8 +40,8 @@ var template= context.JK.fillTemplate($('#template-account-profile-avatar').html(), { "fp_apikey" : gon.fp_apikey, "data-fp-store-path" : createStorePath(userDetail) + createOriginalFilename(userDetail), - "fp_policy" : filepicker_policy.policy, - "fp_signature" : filepicker_policy.signature + "fp_policy" : encodeURIComponent(filepicker_policy.policy), + "fp_signature" : encodeURIComponent(filepicker_policy.signature) }); $('#account-profile-avatar-content-scroller').html(template); @@ -202,7 +202,6 @@ renderNoAvatar(avatarSpace); } else { - rest.getFilepickerPolicy({handle: fpfile.url}) .done(function(filepickerPolicy) { avatarSpace.children().remove(); diff --git a/web/app/assets/javascripts/backend_alerts.js b/web/app/assets/javascripts/backend_alerts.js index bf5b1993b..2bd2b3737 100644 --- a/web/app/assets/javascripts/backend_alerts.js +++ b/web/app/assets/javascripts/backend_alerts.js @@ -137,6 +137,9 @@ else if(type === ALERT_NAMES.VIDEO_WINDOW_CLOSED) { context.VideoActions.videoWindowClosed() } + else if (type === ALERT_NAMES.VST_CHANGED) { + context.ConfigureTracksActions.onVstChanged() + } else if((!context.JK.CurrentSessionModel || !context.JK.CurrentSessionModel.inSession()) && (ALERT_NAMES.INPUT_IO_RATE == type || ALERT_NAMES.INPUT_IO_JTR == type || ALERT_NAMES.OUTPUT_IO_RATE == type || ALERT_NAMES.OUTPUT_IO_JTR== type)) { // squelch these events if not in session diff --git a/web/app/assets/javascripts/checkout_complete.js b/web/app/assets/javascripts/checkout_complete.js index 4f1e96110..aa52214b9 100644 --- a/web/app/assets/javascripts/checkout_complete.js +++ b/web/app/assets/javascripts/checkout_complete.js @@ -15,6 +15,7 @@ var $templatePurchasedJamTrack = null; var $thanksPanel = null; var $jamTrackInBrowser = null; + var $giftCardPurchased = null; var $purchasedJamTrack = null; var $purchasedJamTrackHeader = null; var $purchasedJamTracks = null; @@ -75,9 +76,17 @@ else { $thanksPanel.removeClass('hidden') handleJamTracksPurchased(purchaseResponse.jam_tracks) + handleGiftCardsPurchased(purchaseResponse.gift_cards) } } + + function handleGiftCardsPurchased(gift_cards) { + // were any GiftCards purchased? + if(gift_cards && gift_cards.length > 0) { + $giftCardPurchased.removeClass('hidden') + } + } function handleJamTracksPurchased(jamTracks) { // were any JamTracks purchased? var jamTracksPurchased = jamTracks && jamTracks.length > 0; @@ -194,6 +203,7 @@ $templatePurchasedJamTrack = $('#template-purchased-jam-track'); $thanksPanel = $screen.find(".thanks-panel"); $jamTrackInBrowser = $screen.find(".thanks-detail.jam-tracks-in-browser"); + $giftCardPurchased = $screen.find('.thanks-detail.gift-card') $purchasedJamTrack = $thanksPanel.find(".thanks-detail.purchased-jam-track"); $purchasedJamTrackHeader = $purchasedJamTrack.find(".purchased-jam-track-header"); $purchasedJamTracks = $purchasedJamTrack.find(".purchased-list") diff --git a/web/app/assets/javascripts/checkout_order.js b/web/app/assets/javascripts/checkout_order.js index 168b965d6..a69c0a39c 100644 --- a/web/app/assets/javascripts/checkout_order.js +++ b/web/app/assets/javascripts/checkout_order.js @@ -135,15 +135,7 @@ } } - function displayTax(effectiveQuantity, item_tax, total_with_tax) { - var totalTax = 0; - var totalPrice = 0; - - var unitTax = item_tax * effectiveQuantity; - totalTax += unitTax; - - var totalUnitPrice = total_with_tax * effectiveQuantity; - totalPrice += totalUnitPrice; + function displayTax(totalTax, totalPrice) { $screen.find('.order-right-page .order-items-value.taxes').text('$' + totalTax.toFixed(2)) $screen.find('.order-right-page .order-items-value.grand-total').text('$' + totalPrice.toFixed(2)) @@ -181,8 +173,16 @@ taxRate = 0.0825; } - var unitTax = 1.99 * taxRate; - displayTax(effectiveQuantity, unitTax, 1.99 + unitTax) + var estimatedTax = 0; + var estimatedTotal = 0; + + context._.each(carts, function(cart) { + var cart_quantity = cart.product_info.quantity - cart.product_info.marked_for_redeem + estimatedTax += cart.product_info.price * cart_quantity * taxRate; + estimatedTotal += cart.product_info.price * cart_quantity; + }) + + displayTax(Math.round(estimatedTax*100)/100, Math.round((estimatedTotal + estimatedTax)*100)/100) } else { checkoutUtils.configureRecurly() diff --git a/web/app/assets/javascripts/checkout_payment.js b/web/app/assets/javascripts/checkout_payment.js index 9b72f4fc5..956c11886 100644 --- a/web/app/assets/javascripts/checkout_payment.js +++ b/web/app/assets/javascripts/checkout_payment.js @@ -95,7 +95,7 @@ $reuseExistingCardChk.iCheck(userDetail.reuse_card && userDetail.has_recurly_account ? 'check' : 'uncheck').attr('checked', userDetail.reuse_card) // show appropriate prompt text based on whether user has a free jamtrack - if(user.free_jamtrack) { + if(user.has_redeemable_jamtrack) { $freeJamTrackPrompt.removeClass('hidden') } else { diff --git a/web/app/assets/javascripts/checkout_utils.js.coffee b/web/app/assets/javascripts/checkout_utils.js.coffee index c16e09f9b..c789ba71d 100644 --- a/web/app/assets/javascripts/checkout_utils.js.coffee +++ b/web/app/assets/javascripts/checkout_utils.js.coffee @@ -55,6 +55,16 @@ class CheckoutUtils return carts[0].product_info.free + hasOnlyFreeItemsInShoppingCart: (carts) => + if carts.length == 0 + return false + + for cart in carts + if !cart.product_info.free + return false + + return true + configureRecurly: () => unless @configuredRecurly context.recurly.configure(gon.global.recurly_public_api_key) diff --git a/web/app/assets/javascripts/clientUpdate.js b/web/app/assets/javascripts/clientUpdate.js index ca5bdd428..dd3142e76 100644 --- a/web/app/assets/javascripts/clientUpdate.js +++ b/web/app/assets/javascripts/clientUpdate.js @@ -61,7 +61,10 @@ }) } - app.layout.showDialog('client-update') + if(!app.layout.isDialogShowing('client-update')) { + app.layout.showDialog('client-update') + } + //$('#client_update').show() //$('#client_update_overlay').show() } @@ -192,6 +195,11 @@ function runCheck(product, version, uri, size, currentVersion) { + if (app.clientUpdating) { + logger.debug("client is already updating; skipping") + return + } + if(currentVersion === undefined) { currentVersion = context.jamClient.ClientUpdateVersion(); @@ -302,7 +310,7 @@ $(document).on(EVENTS.SESSION_ENDED, function(e, data){ if(app.clientUpdating) { - updateClientUpdateDialog("update-start", { uri: updateUri }) + updateClientUpdateDialog("update-start", { uri: updateUri}) } }); diff --git a/web/app/assets/javascripts/dialog/gettingStartedDialog.js b/web/app/assets/javascripts/dialog/gettingStartedDialog.js index 867a411f2..aa35e80e7 100644 --- a/web/app/assets/javascripts/dialog/gettingStartedDialog.js +++ b/web/app/assets/javascripts/dialog/gettingStartedDialog.js @@ -50,7 +50,7 @@ $browserJamTrackBtn.click(function() { app.layout.closeDialog('getting-started') - window.location = '/client#/jamtrack/search' + window.location = '/client#/jamtrack' return false; }) @@ -69,9 +69,9 @@ function beforeShow() { app.user().done(function(user) { - var jamtrackRule = user.free_jamtrack ? 'has-free-jamtrack' : 'no-free-jamtrack' + var jamtrackRule = user.has_redeemable_jamtrack ? 'has-free-jamtrack' : 'no-free-jamtrack' $jamTrackSection.removeClass('has-free-jamtrack').removeClass('no-free-jamtrack').addClass(jamtrackRule) - if(user.free_jamtrack) { + if(user.has_redeemable_jamtrack) { $jamTracksLimitedTime.removeClass('hidden') } }) diff --git a/web/app/assets/javascripts/download_jamtrack.js.coffee b/web/app/assets/javascripts/download_jamtrack.js.coffee index c4727631d..eb9846546 100644 --- a/web/app/assets/javascripts/download_jamtrack.js.coffee +++ b/web/app/assets/javascripts/download_jamtrack.js.coffee @@ -369,8 +369,8 @@ context.JK.DownloadJamTrack = class DownloadJamTrack @trackDetail = context.jamClient.JamTrackGetTrackDetail ("#{@jamTrack.id}-#{@sampleRateForFilename}") if @trackDetail.version? - @logger.error("after invalidating package, the version is still wrong!") - throw "after invalidating package, the version is still wrong!" + @logger.error("after invalidating package, the version is still wrong!", @trackDetail) + throw "after invalidating package, the version is still wrong! #{@trackDetail.version}" switch @trackDetail.key_state when 'pending' diff --git a/web/app/assets/javascripts/globals.js b/web/app/assets/javascripts/globals.js index c085e548b..8c5895a7f 100644 --- a/web/app/assets/javascripts/globals.js +++ b/web/app/assets/javascripts/globals.js @@ -126,7 +126,8 @@ RECORDING_DONE :48, //the recording writer thread is done VIDEO_WINDOW_OPENED :49, //video window opened VIDEO_WINDOW_CLOSED :50, - LAST_ALERT : 51 + VST_CHANGED: 51, // VST state changed + LAST_ALERT : 52 } // recreate eThresholdType enum from MixerDialog.h context.JK.ALERT_TYPES = { @@ -190,7 +191,8 @@ 48: {"title": "", "message": ""}, // RECORDING_DONE 49: {"title": "", "message": ""}, // VIDEO_WINDOW_OPENED 50: {"title": "", "message": ""}, // VIDEO_WINDOW_CLOSED - 51: {"title": "", "message": ""} // LAST_ALERT + 51: {"title": "", "message": ""}, // VST_CHANGED + 52: {"title": "", "message": ""} // LAST_ALERT }; // add the alert's name to the ALERT_TYPES structure diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js index 2dcaabfc3..10461d496 100644 --- a/web/app/assets/javascripts/jam_rest.js +++ b/web/app/assets/javascripts/jam_rest.js @@ -1187,6 +1187,7 @@ }); deferred.done(function(user) { + context.JK.currentUserFreeJamTrack = user.show_free_jamtrack window.UserActions.loaded(user) }) @@ -1791,12 +1792,28 @@ } function addJamtrackToShoppingCart(options) { - return $.ajax({ + var deferred = $.ajax({ type: "POST", url: '/api/shopping_carts/add_jamtrack?' + $.param(options), dataType: "json", contentType: 'application/json' }); + + deferred.done(function(response) { + window.UserActions.modify(response) + }) + return deferred + } + + function addGiftCardToShoppingCart(options) { + var deferred = $.ajax({ + type: "POST", + url: '/api/shopping_carts/add_gift_card?' + $.param(options), + dataType: "json", + contentType: 'application/json' + }); + + return deferred } function getShoppingCarts() { @@ -1810,12 +1827,17 @@ } function removeShoppingCart(options) { - return $.ajax({ + var deferred = $.ajax({ type: "DELETE", url: '/api/shopping_carts?' + $.param(options), dataType: "json", contentType: 'application/json' }) + + deferred.done(function(response) { + window.UserActions.modify(response) + }) + return deferred } function clearShoppingCart(options) { @@ -1986,6 +2008,17 @@ }); } + function redeemGiftCard(data) { + var id = getId(data); + return $.ajax({ + type: "POST", + url: '/api/users/' + id + '/gift_cards', + dataType: "json", + contentType: 'application/json', + data: JSON.stringify(data), + }); + } + function portOverCarts() { return $.ajax({ type: "POST", @@ -2166,6 +2199,7 @@ this.enqueueJamTrack = enqueueJamTrack; this.getBackingTracks = getBackingTracks; this.addJamtrackToShoppingCart = addJamtrackToShoppingCart; + this.addGiftCardToShoppingCart = addGiftCardToShoppingCart; this.getShoppingCarts = getShoppingCarts; this.removeShoppingCart = removeShoppingCart; this.clearShoppingCart = clearShoppingCart; @@ -2190,6 +2224,7 @@ this.playJamTrack = playJamTrack; this.createSignupHint = createSignupHint; this.createAlert = createAlert; + this.redeemGiftCard = redeemGiftCard; this.signup = signup; this.portOverCarts = portOverCarts; return this; diff --git a/web/app/assets/javascripts/jam_track_screen.js.coffee b/web/app/assets/javascripts/jam_track_screen.js.coffee deleted file mode 100644 index bb0c2fa2b..000000000 --- a/web/app/assets/javascripts/jam_track_screen.js.coffee +++ /dev/null @@ -1,484 +0,0 @@ -$ = jQuery -context = window -context.JK ||= {} - -context.JK.JamTrackScreen=class JamTrackScreen - LIMIT = 10 - instrument_logo_map = context.JK.getInstrumentIconMap24() - - constructor: (@app) -> - @EVENTS = context.JK.EVENTS - @logger = context.JK.logger - @screen = null - @content = null - @scroller = null - @genre = null - @artist = null - @instrument = null - @availability = null - @nextPager = null - @noMoreJamtracks = null - @currentPage = 0 - @next = null - @currentQuery = this.defaultQuery() - @expanded = null - @shownHelperBubbles = false - - beforeShow:(data) => - this.setFilterFromURL() - - if context.JK.currentUserId? - @app.user().done((user) => - @user = user - this.refresh() - ).fail((arg) => - @logger.error("app.user.done failed: " + JSON.stringify(arg)) - - @logger.debug(arg.statusCode); - - throw 'fail should not occur if user is available' - ) - else - this.refresh() - unless @shownHelperBubbles - @shownHelperBubbles = true - @startHelperBubbles() - - afterShow:(data) => - context.JK.Tracking.jamtrackBrowseTrack(@app) - - beforeHide: () => - this.clearCtaHelpTimeout() - this.clearBandFilterHelpTimeout() - this.clearMasterHelpTimeout() - this.clearResults(); - - events:() => - @genre.on 'change', this.search - @artist.on 'change', this.search - @instrument.on 'change', this.search - @availability.on 'change', this.search - - clearResults:() => - @currentPage = 0 - @content.empty() - @noMoreJamtracks.hide() - @next = null - - startHelperBubbles: () => - @showBandFilterHelpTimeout = setTimeout(@showBandFilterHelp, 3500) - - showBandFilterHelp: () => - context.JK.HelpBubbleHelper.jamtrackBrowseBand(@artist.closest('.easydropdown-wrapper'), $('body')) - - @showMasterHelpDueTime = new Date().getTime() + 11000 # 6000 ms for band tooltip to display, and 5 seconds of quiet time - @scroller.on('scroll', @masterHelpScrollWatch) - @scroller.on('scroll', @clearBubbles) - @showMasterHelpTimeout = setTimeout(@showMasterHelp, @masterHelpDueTime()) - - clearBubbles: () => - if @helpBubble? - @helpBubble.btOff() - @helpBubble = null - - # computes when we should show the master help bubble - masterHelpDueTime: () => - dueTime = @showMasterHelpDueTime - new Date().getTime() - if dueTime <= 0 - dueTime = 2000 - dueTime - - - # computes when we should show the master help bubble - ctaHelpDueTime: () => - dueTime = @showCtaHelpDueTime - new Date().getTime() - if dueTime <= 0 - dueTime = 2000 - dueTime - - # if the user scrolls, reset the master help due time - masterHelpScrollWatch: () => - @clearMasterHelpTimeout() - @showMasterHelpTimeout = setTimeout(@showMasterHelp, @masterHelpDueTime() + 2000) - - # if the user scrolls, reset the master help due time - ctaHelpScrollWatch: () => - @clearCtaHelpTimeout() - @showCtaHelpTimeout = setTimeout(@showCtaHelp, @ctaHelpDueTime() + 2000) - - - showCtaHelp: () => - @scroller.off('scroll', @ctaHelpScrollWatch) - @clearCtaHelpTimeout() - - cutoff = @scroller.offset().top; - - @screen.find('.jamtrack-actions').each((i, element) => - $element = $(element) - - if ($element.offset().top >= cutoff) - @helpBubble = context.JK.HelpBubbleHelper.jamtrackBrowseCta($element, $('body')) - return false - else - return true - ) - - showMasterHelp: () => - @scroller.off('scroll', @masterHelpScrollWatch) - @clearMasterHelpTimeout() - - # don't show the help if the user has already clicked a preview - unless @userPreviewed - cutoff = @scroller.offset().top; - - @screen.find('.jamtrack-preview[data-track-type="Master"]').each((i, element) => - $element = $(element) - - if ($element.offset().top >= cutoff) - @helpBubble = context.JK.HelpBubbleHelper.jamtrackBrowseMasterMix($element.find('.play-button'), $('body')) - return false - else - return true - ) - - @showCtaHelpDueTime = new Date().getTime() + 11000 - @scroller.on('scroll', @ctaHelpScrollWatch) - @showCtaHelpTimeout = setTimeout(@showCtaHelp, @ctaHelpDueTime()) # 6000 ms for bubble show time, and 5000ms for delay - - previewPlayed: () => - @userPreviewed = true - - clearCtaHelpTimeout:() => - if @showCtaHelpTimeout? - clearTimeout(@showCtaHelpTimeout) - @showCtaHelpTimeout = null - - clearBandFilterHelpTimeout: () => - if @showBandFilterHelpTimeout? - clearTimeout(@showBandFilterHelpTimeout) - @showBandFilterHelpTimeout = null - - clearMasterHelpTimeout: () => - if @showMasterHelpTimeout? - clearTimeout(@showMasterHelpTimeout) - @showMasterHelpTimeout = null - - setFilterFromURL:() => - # Grab parms from URL for artist, instrument, and availability - parms=this.getParams() - - if(parms.artist?) - @artist.val(parms.artist) - else - @artist.val('') - if(parms.instrument?) - @instrument.val(parms.instrument) - else - @instrument.val('') - if(parms.availability?) - @availability.val(parms.availability) - else - @availability.val('') - - if window.history.replaceState #ie9 proofing - window.history.replaceState({}, "", "/client#/jamtrackBrowse") - - getParams:() => - params = {} - q = window.location.href.split("?")[1] - if q? - q = q.split('#')[0] - raw_vars = q.split("&") - for v in raw_vars - [key, val] = v.split("=") - params[key] = decodeURIComponent(val) - params - - setFilterState: (state) => - if state - @genre.easyDropDown('enable').removeAttr('disabled') - @artist.easyDropDown('enable').removeAttr('disabled') - @instrument.easyDropDown('enable').removeAttr('disabled') - @availability.easyDropDown('enable').removeAttr('disabled') - else - @genre.easyDropDown('disable').attr('disabled', 'disabled') - @artist.easyDropDown('disable').attr('disabled', 'disabled') - @instrument.easyDropDown('disable').attr('disabled', 'disabled') - @availability.easyDropDown('disable').attr('disabled', 'disabled') - - refresh:() => - this.clearResults() - @currentQuery = this.buildQuery() - that = this - this.setFilterState(false) - rest.getJamTracks(@currentQuery).done((response) => - that.handleJamtrackResponse(response) - ).fail( (jqXHR) => - that.clearResults() - that.noMoreJamtracks.show() - that.app.notifyServerError jqXHR, 'Jamtrack Unavailable' - ).always () => - that.setFilterState(true) - - search:() => - this.refresh() - false - - defaultQuery:() => - query = - per_page: LIMIT - page: @currentPage+1 - if @next - query.since = @next - query - - buildQuery:() => - @currentQuery = this.defaultQuery() - # genre filter - # var genres = @screen.find('#jamtrack_genre').val() - # if (genres !== undefined) { - # @currentQuery.genre = genres - # } - # instrument filter - - instrument = @instrument.val() - if instrument? - @currentQuery.instrument = instrument - - # artist filter - art = @artist.val() - if art? - @currentQuery.artist = art - - # availability filter - availability = @availability.val() - if availability? - @currentQuery.availability = availability - @currentQuery - - handleJamtrackResponse:(response) => - @next = response.next - this.renderJamtracks(response) - if response.next == null - # if we less results than asked for, end searching - @scroller.infinitescroll 'pause' - if @currentPage == 0 and response.jamtracks.length == 0 - @content.append 'Loading ...') - img: '/assets/shared/spinner.gif' - path: (page) => - '/api/jamtracks?' + $.param(that.buildQuery()) - - }, (json, opts) => - this.handleJamtrackResponse(json) - @scroller.infinitescroll 'resume' - - playJamtrack:(e) => - e.preventDefault() - - addToCartJamtrack:(e) => - e.preventDefault() - $target = $(e.target) - params = id: $target.attr('data-jamtrack-id') - isFree = $(e.target).is('.is_free') - - rest.addJamtrackToShoppingCart(params).done((response) => - if(isFree) - if context.JK.currentUserId? - context.JK.currentUserFreeJamTrack = true # make sure the user sees no more free notices - context.location = '/client#/redeemComplete' - else - # now make a rest call to buy it - context.location = '/client#/redeemSignup' - - else - context.location = '/client#/shoppingCart' - - ).fail @app.ajaxError - - licenseUSWhy:(e) => - e.preventDefault() - @app.layout.showDialog 'jamtrack-availability-dialog' - - handleExpanded:(trackElement) => - jamTrack = trackElement.data('jamTrack') - expanded = trackElement.data('expanded') - expand = !expanded - trackElement.data('expanded', expand) - - detailArrow = trackElement.find('.jamtrack-detail-btn') - - if expand - trackElement.find('.extra').removeClass('hidden') - detailArrow.html('hide tracks ') - for track in jamTrack.tracks - trackElement.find("[jamtrack-track-id='#{track.id}']").removeClass('hidden') - else - trackElement.find('.extra').addClass('hidden') - detailArrow.html('show all tracks ') - count = 0 - for track in jamTrack.tracks - if count < 6 - trackElement.find("[jamtrack-track-id='#{track.id}']").removeClass('hidden') - else - trackElement.find("[jamtrack-track-id='#{track.id}']").addClass('hidden') - count++ - - - registerEvents:(parent) => - #@screen.find('.jamtrack-detail-btn').on 'click', this.showJamtrackDescription - parent.find('.play-button').on 'click', this.playJamtrack - parent.find('.jamtrack-add-cart').on 'click', this.addToCartJamtrack - parent.find('.license-us-why').on 'click', this.licenseUSWhy - parent.find('.jamtrack-detail-btn').on 'click', this.toggleExpanded - # @screen.find('.jamtrack-preview').each (index, element) => - # new JK.JamTrackPreview(data.app, $element, jamTrack, track, {master_shows_duration: true}) - - rerenderJamtracks:() => - if @currentData? - @clearResults() - @renderJamtracks(@currentData) - false - - computeWeight: (jam_track_track, instrument) => - weight = switch - when jam_track_track.track_type == 'Master' then 0 - when jam_track_track.instrument?.id == instrument then 1 + jam_track_track.position - else 10000 + jam_track_track.position - - renderJamtracks:(data) => - @currentData = data - that = this - - for jamtrack in data.jamtracks - jamtrackExpanded = this.expanded==jamtrack.id - trackRow = _.clone(jamtrack) - trackRow.track_cnt = jamtrack.tracks.length - trackRow.tracks = [] - - # if an instrument is selected by the user, then re-order any jam tracks with a matching instrument to the top - instrument = @instrument.val() - if instrument? - jamtrack.tracks.sort((a, b) => - aWeight = @computeWeight(a, instrument) - bWeight = @computeWeight(b, instrument) - return aWeight - bWeight - ) - - for track in jamtrack.tracks - trackRow.tracks.push(track) - if track.track_type=='Master' - track.instrument_desc = "Master" - else - inst = '../assets/content/icon_instrument_default24.png' - if track.instrument? - if track.instrument.id in instrument_logo_map - inst = instrument_logo_map[track.instrument.id].asset - track.instrument_desc = track.instrument.description - track.instrument_url = inst - - if track.part != '' - track.instrument_desc += ' (' + track.part + ')' - - free_state = if context.JK.currentUserFreeJamTrack then 'free' else 'non-free' - - is_free = free_state == 'free' - - options = - jamtrack: trackRow - expanded: false - free_state: free_state, - is_free: is_free - @jamtrackItem = $(context._.template($('#template-jamtrack').html(), options, variable: 'data')) - that.renderJamtrack(@jamtrackItem, jamtrack) - that.registerEvents(@jamtrackItem) - - - renderJamtrack:(jamtrackElement, jamTrack) => - jamtrackElement.data('jamTrack', jamTrack) - jamtrackElement.data('expanded', true) - - @content.append jamtrackElement - - #if this.expanded==jamTrack.id - for track in jamTrack.tracks - trackRow = jamtrackElement.find("[jamtrack-track-id='#{track.id}']") - previewElement = trackRow.find(".jamtrack-preview") - preview = new JK.JamTrackPreview(@app, previewElement, jamTrack, track, {master_shows_duration: true, color:'gray'}) - $(preview).on(@EVENTS.PREVIEW_PLAYED, @previewPlayed) - - this.handleExpanded(jamtrackElement, false) - - showJamtrackDescription:(e) => - e.preventDefault() - @description = $(e.target).parent('.detail-arrow').next() - if @description.css('display') == 'none' - @description.show() - else - @description.hide() - - toggleExpanded:(e) => - e.preventDefault() - jamtrackRecord = $(e.target).parents('.jamtrack-record') - jamTrackId = jamtrackRecord.attr("jamtrack-id") - - this.handleExpanded(jamtrackRecord) - - initialize:() => - - screenBindings = - 'beforeShow': this.beforeShow - 'afterShow': this.afterShow - 'beforeHide' : this.beforeHide - @app.bindScreen 'jamtrackBrowse', screenBindings - @screen = $('#jamtrack-find-form') - @scroller = @screen.find('.content-body-scroller') - @content = @screen.find('.jamtrack-content') - @genre = @screen.find('#jamtrack_genre') - @artist = @screen.find('#jamtrack_artist') - @instrument = @screen.find('#jamtrack_instrument') - @availability = @screen.find('#jamtrack_availability') - @nextPager = @screen.find('a.btn-next-pager') - @noMoreJamtracks = @screen.find('.end-of-jamtrack-list') - if @screen.length == 0 - throw new Error('@screen must be specified') - if @scroller.length == 0 - throw new Error('@scroller must be specified') - if @content.length == 0 - throw new Error('@content must be specified') - if @noMoreJamtracks.length == 0 - throw new Error('@noMoreJamtracks must be specified') - #if(@genre.length == 0) throw new Error("@genre must be specified") - - if @artist.length == 0 - throw new Error('@artist must be specified') - if @instrument.length == 0 - throw new Error('@instrument must be specified') - #if @availability.length == 0 - # throw new Error('@availability must be specified') - this.events() - - diff --git a/web/app/assets/javascripts/jamtrack_landing.js.coffee b/web/app/assets/javascripts/jamtrack_landing.js.coffee deleted file mode 100644 index 73cb6d3fe..000000000 --- a/web/app/assets/javascripts/jamtrack_landing.js.coffee +++ /dev/null @@ -1,80 +0,0 @@ -$ = jQuery -context = window -context.JK ||= {} - -context.JK.JamTrackLanding = class JamTrackLanding - constructor: (@app) -> - @rest = context.JK.Rest() - @client = context.jamClient - @logger = context.JK.logger - @screen = null - @noFreeJamTrack = null - @freeJamTrack = null - @bandList = null - @noBandsFound = null - - initialize:() => - screenBindings = - 'beforeShow': @beforeShow - 'afterShow': @afterShow - - #@app.bindScreen('jamtrackLanding', screenBindings) - @screen = $('#jamtrackLanding') - @noFreeJamTrack = @screen.find('.no-free-jamtrack') - @freeJamTrack = @screen.find('.free-jamtrack') - @bandList = @screen.find('#band_list') - @noBandsFound = @screen.find('#no_bands_found') - - beforeShow:() => - - @noFreeJamTrack.addClass('hidden') - @freeJamTrack.addClass('hidden') - - afterShow:() => - - if context.JK.currentUserId - @app.user().done(@onUser) - else - @onUser({free_jamtrack: gon.global.one_free_jamtrack_per_user}) - - onUser:(user) => - if user.free_jamtrack - @freeJamTrack.removeClass('hidden') - else - @noFreeJamTrack.removeClass('hidden') - - # Get artist names and build links - @rest.getJamTrackArtists({group_artist: true, per_page:100}) - .done(this.buildArtistLinks) - .fail(this.handleFailure) - - # Bind links to action that will open the jam_tracks list view filtered to given artist_name: - # artist_name - this.bindArtistLinks() - - buildArtistLinks:(response) => - # Get artist names and build links - @logger.debug("buildArtest links response", response) - - artists = response.artists - $("#band_list>li:not('#no_bands_found')").remove() - if artists.length==0 - @noBandsFound.removeClass("hidden") - else - @noBandsFound.addClass("hidden") - - # client#/jamtrack - for artist in artists - artistLink = "#{artist.original_artist} (#{artist.song_count})" - @bandList.append("
  • #{artistLink}
  • ") - - # We don't want to do a full page load if this is clicked on here: - bindArtistLinks:() => - that=this - @bandList.on "click", "a.artist-link", (event)-> - context.location="client#/jamtrack/search" - if window.history.replaceState # ie9 proofing - window.history.replaceState({}, "", this.href) - event.preventDefault() - - handleFailure:(error) => diff --git a/web/app/assets/javascripts/order.js b/web/app/assets/javascripts/order.js deleted file mode 100644 index 23360c626..000000000 --- a/web/app/assets/javascripts/order.js +++ /dev/null @@ -1,670 +0,0 @@ -(function(context,$) { - - "use strict"; - context.JK = context.JK || {}; - context.JK.OrderScreen = function(app) { - - var EVENTS = context.JK.EVENTS; - var logger = context.JK.logger; - - var $screen = null; - var $templateOrderContent = null; - var $templatePurchasedJamTrack = null; - var $navigation = null; - var $billingInfo = null; - var $shippingInfo = null; - var $paymentMethod = null; - var $shippingAddress = null; - var $shippingAsBilling = null; - var $paymentInfoPanel = null; - var $orderPanel = null; - var $thanksPanel = null; - var $jamTrackInBrowser = null; - var $purchasedJamTrack = null; - var $purchasedJamTrackHeader = null; - var $purchasedJamTracks = null; - var $orderContent = null; - var userDetail = null; - var step = null; - var billing_info = null; - var shipping_info = null; - var shipping_as_billing = null; - var downloadJamTracks = []; - var purchasedJamTracks = null; - var purchasedJamTrackIterator = 0; - - function beforeShow() { - beforeShowPaymentInfo(); - resetJamTrackDownloadInfo(); - } - - function beforeShowPaymentInfo() { - step = 2; - renderNavigation(); - renderAccountInfo(); - $("#order_error").addClass("hidden") - } - - function resetJamTrackDownloadInfo() { - $purchasedJamTrack.addClass('hidden'); - $purchasedJamTracks.children().remove() - $jamTrackInBrowser.hide('hidden'); - } - - function renderAccountInfo() { - rest.getUserDetail() - .done(populateAccountInfo) - .error(app.ajaxError); - } - - function populateAccountInfo(user) { - userDetail = user; - - if (userDetail.has_recurly_account) { - rest.getBillingInfo() - .done(function(response) { - $billingInfo.find("#billing-first-name").val(response.first_name); - $billingInfo.find("#billing-last-name").val(response.last_name); - $billingInfo.find("#billing-address1").val(response.address1); - $billingInfo.find("#billing-address2").val(response.address2); - $billingInfo.find("#billing-city").val(response.city); - $billingInfo.find("#billing-state").val(response.state); - $billingInfo.find("#billing-zip").val(response.zip); - $billingInfo.find("#billing-country").val(response.country); - - $shippingAddress.find("#shipping-first-name").val(response.first_name); - $shippingAddress.find("#shipping-last-name").val(response.last_name); - $shippingAddress.find("#shipping-address1").val(response.address1); - $shippingAddress.find("#shipping-address2").val(response.address2); - $shippingAddress.find("#shipping-city").val(response.city); - $shippingAddress.find("#shipping-state").val(response.state); - $shippingAddress.find("#shipping-zip").val(response.zip); - $shippingAddress.find("#shipping-country").val(response.country); - }) - .error(app.ajaxError); - } - else { - $billingInfo.find("#billing-first-name").val(userDetail.first_name); - $billingInfo.find("#billing-last-name").val(userDetail.last_name); - $billingInfo.find("#billing-city").val(userDetail.city); - $billingInfo.find("#billing-state").val(userDetail.state); - $billingInfo.find("#billing-country").val(userDetail.country); - - $shippingAddress.find("#shipping-first-name").val(userDetail.first_name); - $shippingAddress.find("#shipping-last-name").val(userDetail.last_name); - $shippingAddress.find("#shipping-city").val(userDetail.city); - $shippingAddress.find("#shipping-state").val(userDetail.state); - $shippingAddress.find("#shipping-country").val(userDetail.country); - } - } - - function afterShow(data) { - // XXX : style-test code - // moveToThanks({jam_tracks: [{id: 14, jam_track_right_id: 11, name: 'Back in Black'}, {id: 15, jam_track_right_id: 11, name: 'In Bloom'}, {id: 16, jam_track_right_id: 11, name: 'Love Bird Supreme'}]}); - } - - function beforeHide() { - if(downloadJamTracks) { - context._.each(downloadJamTracks, function(downloadJamTrack) { - downloadJamTrack.destroy(); - downloadJamTrack.root.remove(); - }) - - downloadJamTracks = []; - } - purchasedJamTracks = null; - purchasedJamTrackIterator = 0; - } - - // TODO: Refactor: this function is long and fraught with many return points. - function next(e) { - e.preventDefault(); - $("#order_error").addClass("hidden") - - // validation - var billing_first_name = $billingInfo.find("#billing-first-name").val(); - var billing_last_name = $billingInfo.find("#billing-last-name").val(); - var billing_address1 = $billingInfo.find("#billing-address1").val(); - var billing_address2 = $billingInfo.find("#billing-address2").val(); - var billing_city = $billingInfo.find("#billing-city").val(); - var billing_state = $billingInfo.find("#billing-state").val(); - var billing_zip = $billingInfo.find("#billing-zip").val(); - var billing_country = $billingInfo.find("#billing-country").val(); - - if (!billing_first_name) { - $billingInfo.find('#divBillingFirstName .error-text').remove(); - $billingInfo.find('#divBillingFirstName').addClass("error"); - $billingInfo.find('#billing-first-name').after(""); - - return false; - } - else { - $billingInfo.find('#divBillingFirstName').removeClass("error"); - } - - if (!billing_last_name) { - $billingInfo.find('#divBillingLastName .error-text').remove(); - $billingInfo.find('#divBillingLastName').addClass("error"); - $billingInfo.find('#billing-last-name').after(""); - - return false; - } - else { - $billingInfo.find('#divBillingLastName').removeClass("error"); - } - - if (!billing_address1) { - $billingInfo.find('#divBillingAddress1 .error-text').remove(); - $billingInfo.find('#divBillingAddress1').addClass("error"); - $billingInfo.find('#billing-address1').after(""); - - return false; - } - else { - $billingInfo.find('#divBillingAddress1').removeClass("error"); - } - - if (!billing_zip) { - $billingInfo.find('#divBillingZip .error-text').remove(); - $billingInfo.find('#divBillingZip').addClass("error"); - $billingInfo.find('#billing-zip').after(""); - - return false; - } - else { - $billingInfo.find('#divBillingZip').removeClass("error"); - } - - if (!billing_state) { - $billingInfo.find('#divBillingState .error-text').remove(); - $billingInfo.find('#divBillingState').addClass("error"); - $billingInfo.find('#billing-zip').after(""); - - return false; - } - else { - $billingInfo.find('#divBillingState').removeClass("error"); - } - - if (!billing_city) { - $billingInfo.find('#divBillingCity .error-text').remove(); - $billingInfo.find('#divBillingCity').addClass("error"); - $billingInfo.find('#billing-city').after(""); - - return false; - } - else { - $billingInfo.find('#divBillingCity').removeClass("error"); - } - - if (!billing_country) { - $billingInfo.find('#divBillingCountry .error-text').remove(); - $billingInfo.find('#divBillingCountry').addClass("error"); - $billingInfo.find('#billing-country').after(""); - - return false; - } - else { - $billingInfo.find('#divBillingCountry').removeClass("error"); - } - - shipping_as_billing = $shippingAsBilling.is(":checked"); - var shipping_first_name, shipping_last_name, shipping_address1, shipping_address2; - var shipping_city, shipping_state, shipping_zip, shipping_country; - - if (!shipping_as_billing) { - shipping_first_name = $shippingAddress.find("#shipping-first-name").val(); - shipping_last_name = $shippingAddress.find("#shipping-last-name").val(); - shipping_address1 = $shippingAddress.find("#shipping-address1").val(); - shipping_address2 = $shippingAddress.find("#shipping-address2").val(); - shipping_city = $shippingAddress.find("#shipping-city").val(); - shipping_state = $shippingAddress.find("#shipping-state").val(); - shipping_zip = $shippingAddress.find("#shipping-zip").val(); - shipping_country = $shippingAddress.find("#shipping-country").val(); - - if (!shipping_first_name) { - $shippingAddress.find('#divShippingFirstName .error-text').remove(); - $shippingAddress.find('#divShippingFirstName').addClass("error"); - $shippingAddress.find('#shipping-first-name').after(""); - - return false; - } - else { - $shippingInfo.find('#divShippingFirstName').removeClass("error"); - } - - if (!shipping_last_name) { - $shippingAddress.find('#divShippingLastName .error-text').remove(); - $shippingAddress.find('#divShippingLastName').addClass("error"); - $shippingAddress.find('#shipping-last-name').after(""); - - return false; - } - else { - $shippingInfo.find('#divShippingLastName').removeClass("error"); - } - - if (!shipping_address1) { - $shippingAddress.find('#divShippingAddress1 .error-text').remove(); - $shippingAddress.find('#divShippingAddress1').addClass("error"); - $shippingAddress.find('#shipping-address1').after(""); - - return false; - } - else { - $shippingInfo.find('#divShippingAddress1').removeClass("error"); - } - - if (!shipping_zip) { - $shippingAddress.find('#divShippingZip .error-text').remove(); - $shippingAddress.find('#divShippingZip').addClass("error"); - $shippingAddress.find('#shipping-zip').after(""); - - return false; - } - else { - $shippingInfo.find('#divShippingZip').removeClass("error"); - } - - if (!shipping_state) { - $shippingAddress.find('#divShippingState .error-text').remove(); - $shippingAddress.find('#divShippingState').addClass("error"); - $shippingAddress.find('#shipping-zip').after(""); - - return false; - } - else { - $shippingInfo.find('#divShippingState').removeClass("error"); - } - - if (!shipping_city) { - $shippingAddress.find('#divShippingCity .error-text').remove(); - $shippingAddress.find('#divShippingCity').addClass("error"); - $shippingAddress.find('#shipping-city').after(""); - - return false; - } - else { - $shippingInfo.find('#divShippingCity').removeClass("error"); - } - - if (!shipping_country) { - $shippingAddress.find('#divShippingCountry .error-text').remove(); - $shippingAddress.find('#divShippingCountry').addClass("error"); - $shippingAddress.find('#shipping-country').after(""); - - return false; - } - else { - $shippingAddress.find('#divShippingCountry').removeClass("error"); - } - } - - var card_name = $paymentMethod.find("#card-name").val(); - var card_number = $paymentMethod.find("#card-number").val(); - var card_year = $paymentMethod.find("#card_expire-date_1i").val(); - var card_month = $paymentMethod.find("#card_expire-date_2i").val(); - var card_verify = $paymentMethod.find("#card-verify").val(); - - if (!card_name) { - $paymentMethod.find('#divCardName .error-text').remove(); - $paymentMethod.find('#divCardName').addClass("error"); - $paymentMethod.find('#card-name').after(""); - return false; - } else { - $paymentMethod.find('#divCardName').removeClass("error"); - } - - if (!card_number) { - $paymentMethod.find('#divCardNumber .error-text').remove(); - $paymentMethod.find('#divCardNumber').addClass("error"); - $paymentMethod.find('#card-number').after(""); - return false; - } else if (!$.payment.validateCardNumber(card_number)) { - $paymentMethod.find('#divCardNumber .error-text').remove(); - $paymentMethod.find('#divCardNumber').addClass("error"); - $paymentMethod.find('#card-number').after(""); - return false; - } else { - $paymentMethod.find('#divCardNumber').removeClass("error"); - } - - if (!$.payment.validateCardExpiry(card_month, card_year)) { - $paymentMethod.find('#divCardExpiry .error-text').remove(); - $paymentMethod.find('#divCardExpiry').addClass("error"); - $paymentMethod.find('#card-expiry').after(""); - } else { - $paymentMethod.find('#divCardExpiry').removeClass("error"); - } - - if (!card_verify) { - $paymentMethod.find('#divCardVerify .error-text').remove(); - $paymentMethod.find('#divCardVerify').addClass("error"); - $paymentMethod.find('#card-verify').after(""); - - return false; - } else if(!$.payment.validateCardCVC(card_verify)) { - $paymentMethod.find('#divCardVerify .error-text').remove(); - $paymentMethod.find('#divCardVerify').addClass("error"); - $paymentMethod.find('#card-verify').after(""); - - return false; - } else { - $paymentMethod.find('#divCardVerify').removeClass("error"); - } - - billing_info = {}; - shipping_info = {}; - billing_info.first_name = billing_first_name; - billing_info.last_name = billing_last_name; - billing_info.address1 = billing_address1; - billing_info.address2 = billing_address2; - billing_info.city = billing_city; - billing_info.state = billing_state; - billing_info.country = billing_country; - billing_info.zip = billing_zip; - billing_info.number = card_number; - billing_info.month = card_month; - billing_info.year = card_year; - billing_info.verification_value = card_verify; - - if (shipping_as_billing) { - shipping_info = $.extend({},billing_info); - delete shipping_info.number; - delete shipping_info.month; - delete shipping_info.year; - delete shipping_info.verification_value; - } else { - shipping_info.first_name = shipping_first_name; - shipping_info.last_name = shipping_last_name; - shipping_info.address1 = shipping_address1; - shipping_info.address2 = shipping_address2; - shipping_info.city = shipping_city; - shipping_info.state = shipping_state; - shipping_info.country = shipping_country; - shipping_info.zip = shipping_zip; - } - - $paymentInfoPanel.find("#payment-info-next").addClass("disabled"); - $paymentInfoPanel.find("#payment-info-next").off("click"); - - rest.createRecurlyAccount({billing_info: billing_info}) - .done(function() { - moveToOrder(); - $paymentInfoPanel.find("#payment-info-next").removeClass("disabled"); - $paymentInfoPanel.find("#payment-info-next").on("click", next); - }) - .fail(errorHandling); - } - - function errorHandling(xhr, ajaxOptions, thrownError) { - $.each(xhr.responseJSON.errors, function(key, error) { - if (key == 'number') { - $paymentMethod.find('#divCardNumber .error-text').remove(); - $paymentMethod.find('#divCardNumber').addClass("error"); - $paymentMethod.find('#card-number').after(""); - } - else if (key == 'verification_value') { - $paymentMethod.find('#divCardVerify .error-text').remove(); - $paymentMethod.find('#divCardVerify').addClass("error"); - $paymentMethod.find('#card-verify').after(""); - } - }); - - $paymentInfoPanel.find("#payment-info-next").addClass("disabled"); - $paymentInfoPanel.find("#payment-info-next").on('click', next); - } - - function orderErrorHandling(xhr, ajaxOptions, thrownError) { - var message = "Error submitting payment: " - $.each(xhr.responseJSON.errors, function(key, error) { - message += key + ": " + error - }) - $("#order_error").text(message) - $("#order_error").removeClass("hidden") - $orderContent.find(".place-order").on('click', placeOrder) - } - - function beforeShowOrder() { - step = 3; - renderNavigation(); - populateOrderPage(); - } - - function clearOrderPage() { - $orderContent.empty(); - } - - function populateOrderPage() { - clearOrderPage(); - - rest.getShoppingCarts() - .done(renderOrderPage) - .fail(app.ajaxError); - } - - function renderOrderPage(carts) { - var data = {} - - var sub_total = 0.0 - var taxes = 0.0 - $.each(carts, function(index, cart) { - sub_total += parseFloat(cart.product_info.price) * parseFloat(cart.quantity) - }); - data.grand_total = (sub_total + taxes).toFixed(2) - data.sub_total = sub_total.toFixed(2) - data.taxes = taxes.toFixed(2) - data.carts = carts - data.billing_info = billing_info - data.shipping_info = shipping_info - data.shipping_as_billing = shipping_as_billing - var orderContentHtml = $( - context._.template( - $templateOrderContent.html(), - data, - {variable: 'data'} - ) - ) - - $orderContent.append(orderContentHtml) - - $orderPanel.find(".change-payment-info").on('click', moveToPaymentInfo) - $orderContent.find(".place-order").on('click', placeOrder) - } - - function moveToOrder() { - $paymentInfoPanel.addClass("hidden"); - $orderPanel.removeClass("hidden"); - beforeShowOrder(); - } - - function moveToThanks(purchaseResponse) { - $("#order_error").addClass("hidden") - $paymentInfoPanel.addClass("hidden") - $orderPanel.addClass("hidden") - $thanksPanel.removeClass("hidden") - rest.clearShoppingCart() - beforeShowOrder() - handleJamTracksPurchased(purchaseResponse.jam_tracks) - } - - function handleJamTracksPurchased(jamTracks) { - // were any JamTracks purchased? - var jamTracksPurchased = jamTracks && jamTracks.length > 0; - if(jamTracksPurchased) { - if(gon.isNativeClient) { - startDownloadJamTracks(jamTracks) - } - else { - $jamTrackInBrowser.removeClass('hidden'); - } - } - } - - function startDownloadJamTracks(jamTracks) { - // there can be multiple purchased JamTracks, so we cycle through them - - purchasedJamTracks = jamTracks; - - // populate list of jamtracks purchased, that we will iterate through graphically - context._.each(jamTracks, function(jamTrack) { - var downloadJamTrack = new context.JK.DownloadJamTrack(app, jamTrack, 'small'); - var $purchasedJamTrack = $(context._.template( - $templatePurchasedJamTrack.html(), - jamTrack, - {variable: 'data'} - )); - - $purchasedJamTracks.append($purchasedJamTrack) - - // show it on the page - $purchasedJamTrack.append(downloadJamTrack.root) - - downloadJamTracks.push(downloadJamTrack) - }) - - iteratePurchasedJamTracks(); - } - - function iteratePurchasedJamTracks() { - if(purchasedJamTrackIterator < purchasedJamTracks.length ) { - var downloadJamTrack = downloadJamTracks[purchasedJamTrackIterator++]; - - // make sure the 'purchasing JamTrack' section can be seen - $purchasedJamTrack.removeClass('hidden'); - - // the widget indicates when it gets to any transition; we can hide it once it reaches completion - $(downloadJamTrack).on(EVENTS.JAMTRACK_DOWNLOADER_STATE_CHANGED, function(e, data) { - - if(data.state == downloadJamTrack.states.synchronized) { - logger.debug("jamtrack " + downloadJamTrack.jamTrack.name + " synchronized;") - //downloadJamTrack.root.remove(); - downloadJamTrack.destroy(); - - // go to the next JamTrack - iteratePurchasedJamTracks() - } - }) - - logger.debug("jamtrack " + downloadJamTrack.jamTrack.name + " downloader initializing") - - // kick off the download JamTrack process - downloadJamTrack.init() - - // XXX style-test code - // downloadJamTrack.transitionError("package-error", "The server failed to create your package.") - - } - else { - logger.debug("done iterating over purchased JamTracks") - $purchasedJamTrackHeader.text('All purchased JamTracks have been downloaded successfully! You can now play them in a session.') - } - } - - function moveToPaymentInfo(e) { - e.preventDefault(); - $paymentInfoPanel.removeClass("hidden"); - $orderPanel.addClass("hidden"); - beforeShowPaymentInfo(); - } - - function toggleShippingAsBilling(e) { - e.preventDefault(); - - var shipping_as_billing = $(e.target).is(':checked'); - - if (!shipping_as_billing) { - $shippingAddress.removeClass("hidden"); - } - else { - $shippingAddress.addClass("hidden"); - } - } - - function placeOrder(e) { - e.preventDefault(); - $orderContent.find(".place-order").off('click') - rest.getShoppingCarts() - .done(function(carts) { - var jam_track_ids = _.map(carts, function(cart){ - return cart.product_info.product_id - }) - rest.placeOrder({jam_tracks: jam_track_ids}) - .done(moveToThanks) - .fail(orderErrorHandling); - } - ).fail(app.ajaxError); - } - - function events() { - $paymentInfoPanel.find("#payment-info-next").on('click', next); - $shippingAsBilling.on('ifChanged', toggleShippingAsBilling); - } - - function reset() { - } - - function renderNavigation() { - $navigation.html(""); - var navigationHtml = $( - context._.template( - $('#template-checkout-navigation').html(), - {current: step}, - {variable: 'data'} - ) - ); - - $navigation.append(navigationHtml); - } - - function initializeControls() { - $("form.payment-info").iCheck({ - checkboxClass: 'icheckbox_minimal', - radioClass: 'iradio_minimal', - inheritClass: true - }); - - // Use jquery.payment to limit characters and length: - $paymentMethod.find("#card-number").payment('formatCardNumber'); - $paymentMethod.find("#card-verify").payment('formatCardCVC'); - } - - function initialize() { - var screenBindings = { - 'beforeShow': beforeShow, - 'afterShow': afterShow, - 'beforeHide' : beforeHide - }; - app.bindScreen('order', screenBindings); - - $screen = $("#orderScreen"); - $templateOrderContent = $("#template-order-content"); - $templatePurchasedJamTrack = $('#template-purchased-jam-track'); - $paymentInfoPanel = $screen.find(".checkout-payment-info"); - $orderPanel = $screen.find(".order-panel"); - $thanksPanel = $screen.find(".thanks-panel"); - $jamTrackInBrowser = $screen.find(".thanks-detail.jam-tracks-in-browser"); - $purchasedJamTrack = $thanksPanel.find(".thanks-detail.purchased-jam-track"); - $purchasedJamTrackHeader = $purchasedJamTrack.find(".purchased-jam-track-header"); - $purchasedJamTracks = $purchasedJamTrack.find(".purchased-list") - $navigation = $screen.find(".checkout-navigation-bar"); - $billingInfo = $paymentInfoPanel.find(".billing-address"); - $shippingInfo = $paymentInfoPanel.find(".shipping-address"); - $paymentMethod = $paymentInfoPanel.find(".payment-method"); - $shippingAddress = $paymentInfoPanel.find(".shipping-address-detail"); - $shippingAsBilling = $paymentInfoPanel.find("#shipping-as-billing"); - $orderContent = $orderPanel.find(".order-content"); - - if($screen.length == 0) throw "$screen must be specified"; - if($navigation.length == 0) throw "$navigation must be specified"; - - initializeControls(); - - events(); - } - - this.initialize = initialize; - - return this; - } -})(window,jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/JamTrackFilterScreen.js.jsx.coffee b/web/app/assets/javascripts/react-components/JamTrackFilterScreen.js.jsx.coffee index 82c7ebea6..f5d84ebdf 100644 --- a/web/app/assets/javascripts/react-components/JamTrackFilterScreen.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/JamTrackFilterScreen.js.jsx.coffee @@ -4,17 +4,11 @@ MIX_MODES = context.JK.MIX_MODES @JamTrackFilterScreen = React.createClass({ - mixins: [Reflux.listenTo(@AppStore,"onAppInit")] + mixins: [Reflux.listenTo(@AppStore,"onAppInit"), Reflux.listenTo(@UserStore,"onUserChanged")] LIMIT: 20 instrument_logo_map: context.JK.getInstrumentIconMap24() - computeWeight: (jam_track_track, instrument) -> - weight = switch - when jam_track_track.track_type == 'Master' then 0 - when jam_track_track.instrument?.id == instrument then 1 + jam_track_track.position - else 10000 + jam_track_track.position - render: () -> searchText = if @state.first_search then 'SEARCH' else 'SEARCH AGAIN' @@ -36,19 +30,21 @@ MIX_MODES = context.JK.MIX_MODES ) for track in jamtrack.tracks - trackRow.tracks.push(track) - if track.track_type=='Master' - track.instrument_desc = "Master" - else - inst = '../assets/content/icon_instrument_default24.png' - if track.instrument? - if track.instrument.id in @instrument_logo_map - inst = @instrument_logo_map[track.instrument.id].asset - track.instrument_desc = track.instrument.description - track.instrument_url = inst - if track.part != '' - track.instrument_desc += ' (' + track.part + ')' + if track.track_type == 'Master' || track.track_type == 'Track' + trackRow.tracks.push(track) + if track.track_type == 'Master' + track.instrument_desc = "Master" + else if track.track_type == 'Track' + inst = '../assets/content/icon_instrument_default24.png' + if track.instrument? + if track.instrument.id in @instrument_logo_map + inst = @instrument_logo_map[track.instrument.id].asset + track.instrument_desc = track.instrument.description + track.instrument_url = inst + + if track.part != '' + track.instrument_desc += ' (' + track.part + ')' trackRow.free_state = if @state.is_free then 'free' else 'non-free' @@ -76,10 +72,10 @@ MIX_MODES = context.JK.MIX_MODES ` actionBtn = null - if jamtrack.is_free - actionBtn = ` GET IT FREE!` - else if jamtrack.purchased + if jamtrack.purchased actionBtn = `PURCHASED` + else if jamtrack.is_free + actionBtn = ` GET IT FREE!` else if jamtrack.added_cart actionBtn = `ALREADY IN CART` else @@ -158,15 +154,13 @@ MIX_MODES = context.JK.MIX_MODES ` - - getInitialState: () -> - {search: '', type: 'user-input', jamtracks:[], show_all_artists: false, currentPage: 0, next: null, searching: false, count: 0, is_free: context.JK.currentUserFreeJamTrack} - - clearResults:() -> #@content.empty() #@noMoreJamtracks.hide() - @setState({currentPage: 0, next: null, jamtracks:[], type: 'user-input', searching:false, is_free: context.JK.currentUserFreeJamTrack}) + @setState({currentPage: 0, next: null, jamtracks:[], type: 'user-input', searching:false, is_free: @user.show_free_jamtrack}) + + getInitialState: () -> + {search: '', type: 'user-input', jamtracks:[], show_all_artists: false, currentPage: 0, next: null, searching: false, count: 0, is_free: context.JK.currentUserFreeJamTrack} defaultQuery:(extra) -> @@ -305,16 +299,21 @@ MIX_MODES = context.JK.MIX_MODES isFree = $(e.target).is('.is_free') @rest.addJamtrackToShoppingCart(params).done((response) => - if(isFree) - if context.JK.currentUserId? - context.JK.currentUserFreeJamTrack = true # make sure the user sees no more free notices - context.location = '/client#/redeemComplete' + if context.JK.currentUserId? + if isFree + if @user.has_redeemable_jamtrack + # this is the 1st jamtrack; let's user the user to completion + context.location = '/client#/redeemComplete' + else + # this is must be a user's gifted jamtrack, to treat them normally in that they'll go to the shopping cart + #context.location = '/client#/shoppingCart' + context.location = '/client#/redeemComplete' else - # now make a rest call to buy it - context.location = '/client#/redeemSignup' - + # this user has nothing free; so send them to shopping cart + context.location = '/client#/shoppingCart' else - context.location = '/client#/shoppingCart' + # user not logged in; make them signup + context.location = '/client#/redeemSignup' ).fail(() => @app.ajaxError) @@ -398,4 +397,15 @@ MIX_MODES = context.JK.MIX_MODES 'afterShow': @afterShow @app.bindScreen('jamtrack/filter', screenBindings) + + onUserChanged: (userState) -> + @user = userState?.user + @setState({is_free: @user?.show_free_jamtrack}) + + computeWeight: (jam_track_track, instrument) -> + weight = switch + when jam_track_track.track_type == 'Master' then 0 + when jam_track_track.instrument?.id == instrument then 1 + jam_track_track.position + else 10000 + jam_track_track.position + }) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/JamTrackLandingScreen.js.jsx.coffee b/web/app/assets/javascripts/react-components/JamTrackLandingScreen.js.jsx.coffee index f0cad879d..46a5b272c 100644 --- a/web/app/assets/javascripts/react-components/JamTrackLandingScreen.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/JamTrackLandingScreen.js.jsx.coffee @@ -13,12 +13,13 @@ rest = context.JK.Rest() {user: null, purchasedJamTracks: []} onUserChanged: (userState) -> + @user = userState?.user @onUser(userState.user) if userState.user render: () -> howTo = null - if @state.user?.free_jamtrack + if @user?.purchased_jamtracks_count == 0 && @user?.has_redeemable_jamtrack howTo = `
    @@ -206,11 +207,6 @@ rest = context.JK.Rest() @processUrl() - if !context.JK.currentUserId - @onUser({free_jamtrack: context.JK.currentUserFreeJamTrack}) - - - beforeShow: () -> @setState({user: null}) diff --git a/web/app/assets/javascripts/react-components/JamTrackSearchScreen.js.jsx.coffee b/web/app/assets/javascripts/react-components/JamTrackSearchScreen.js.jsx.coffee index 04b8fcb6b..25eb00088 100644 --- a/web/app/assets/javascripts/react-components/JamTrackSearchScreen.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/JamTrackSearchScreen.js.jsx.coffee @@ -4,7 +4,7 @@ MIX_MODES = context.JK.MIX_MODES @JamTrackSearchScreen = React.createClass({ - mixins: [Reflux.listenTo(@AppStore,"onAppInit")] + mixins: [Reflux.listenTo(@AppStore,"onAppInit"), Reflux.listenTo(@UserStore,"onUserChanged")] LIMIT: 10 instrument_logo_map: context.JK.getInstrumentIconMap24() @@ -35,19 +35,22 @@ MIX_MODES = context.JK.MIX_MODES ) ### for track in jamtrack.tracks - trackRow.tracks.push(track) - if track.track_type=='Master' - track.instrument_desc = "Master" - else - inst = '../assets/content/icon_instrument_default24.png' - if track.instrument? - if track.instrument.id in @instrument_logo_map - inst = @instrument_logo_map[track.instrument.id].asset - track.instrument_desc = track.instrument.description - track.instrument_url = inst - if track.part != '' - track.instrument_desc += ' (' + track.part + ')' + if track.track_type == 'Master' || track.track_type == 'Track' + trackRow.tracks.push(track) + + if track.track_type == 'Master' + track.instrument_desc = "Master" + else if track.track_type == 'Track' + inst = '../assets/content/icon_instrument_default24.png' + if track.instrument? + if track.instrument.id in @instrument_logo_map + inst = @instrument_logo_map[track.instrument.id].asset + track.instrument_desc = track.instrument.description + track.instrument_url = inst + + if track.part != '' + track.instrument_desc += ' (' + track.part + ')' trackRow.free_state = if @state.is_free then 'free' else 'non-free' @@ -91,10 +94,10 @@ MIX_MODES = context.JK.MIX_MODES
    ` actionBtn = null - if jamtrack.is_free - actionBtn = ` GET IT FREE!` - else if jamtrack.purchased + if jamtrack.purchased actionBtn = `PURCHASED` + else if jamtrack.is_free + actionBtn = ` GET IT FREE!` else if jamtrack.added_cart actionBtn = `ALREADY IN CART` else @@ -219,14 +222,13 @@ MIX_MODES = context.JK.MIX_MODES clearResults:() -> - @setState({currentPage: 0, next: null, show_all_artists: false, artists:[], jamtracks:[], type: 'user-input', searching:false, artist: null, song:null, is_free: context.JK.currentUserFreeJamTrack, first_search: true}) + @setState({currentPage: 0, next: null, show_all_artists: false, artists:[], jamtracks:[], type: 'user-input', searching:false, artist: null, song:null, is_free: @user.show_free_jamtrack, first_search: true}) getInitialState: () -> {search: '', type: 'user-input', artists:[], jamtracks:[], show_all_artists: false, currentPage: 0, next: null, searching: false, first_search: true, count: 0, is_free: context.JK.currentUserFreeJamTrack} onSelectChange: (val) -> - #@logger.debug("CHANGE #{val}") return false unless val? @@ -435,18 +437,39 @@ MIX_MODES = context.JK.MIX_MODES isFree = $(e.target).is('.is_free') @rest.addJamtrackToShoppingCart(params).done((response) => - if(isFree) - if context.JK.currentUserId? - context.JK.currentUserFreeJamTrack = true # make sure the user sees no more free notices - context.location = '/client#/redeemComplete' + if context.JK.currentUserId? + if isFree + if @user.has_redeemable_jamtrack + # this is the 1st jamtrack; let's user the user to completion + context.location = '/client#/redeemComplete' + else + # this is must be a user's gifted jamtrack, to treat them normally in that they'll go to the shopping cart + #context.location = '/client#/shoppingCart' + context.location = '/client#/redeemComplete' else - # now make a rest call to buy it - context.location = '/client#/redeemSignup' - + # this user has nothing free; so send them to shopping cart + context.location = '/client#/shoppingCart' else - context.location = '/client#/shoppingCart' + if isFree + # user not logged in; make them signup + context.location = '/client#/redeemSignup' + else + # this user has nothing free; so send them to shopping cart + context.location = '/client#/shoppingCart' - ).fail(() => @app.ajaxError) + + ).fail(((jqxhr) => + + handled = false + if jqxhr.status == 422 + body = JSON.parse(jqxhr.responseText) + if body.errors && body.errors.base + handled = true + context.JK.Banner.showAlert("You can not have a mix of free and non-free items in your shopping cart.

    If you want to add this new item to your shopping cart, then clear out all current items by clicking on the shopping cart icon and clicking 'delete' next to each item.") + if !handled + @app.ajaxError(arguments[0], arguments[1], arguments[2]) + + )) licenseUSWhy:(e) -> e.preventDefault() @@ -510,6 +533,9 @@ MIX_MODES = context.JK.MIX_MODES if search? performSearch = true @search(search.searchType, search.searchData) + else + if !@state.first_search + @search(@state.type, window.JamTrackSearchInput) if performSearch if window.history.replaceState #ie9 proofing @@ -517,11 +543,6 @@ MIX_MODES = context.JK.MIX_MODES beforeShow: () -> - @setState({is_free: context.JK.currentUserFreeJamTrack}) - if !@state.first_search - @search(@state.type, window.JamTrackSearchInput) - - onAppInit: (@app) -> @@ -530,10 +551,14 @@ MIX_MODES = context.JK.MIX_MODES @rest = context.JK.Rest() @logger = context.JK.logger - screenBindings = 'beforeShow': @beforeShow 'afterShow': @afterShow @app.bindScreen('jamtrack/search', screenBindings) + + onUserChanged: (userState) -> + @user = userState?.user + @setState({is_free: @user?.show_free_jamtrack}) + }) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/PopupJamTrackPlayer.js.jsx.coffee b/web/app/assets/javascripts/react-components/PopupJamTrackPlayer.js.jsx.coffee index 4857fdb0b..9f3fd2975 100644 --- a/web/app/assets/javascripts/react-components/PopupJamTrackPlayer.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/PopupJamTrackPlayer.js.jsx.coffee @@ -32,6 +32,11 @@ mixins.push(Reflux.listenTo(JamTrackPlayerStore, 'onJamTrackPlayerStoreChanged') mixins: mixins + computeWeight: (jam_track_track) -> + weight = switch + when jam_track_track.track_type == 'Master' then 0 + when jam_track_track.track_type == 'Click' then 10000 + else jam_track_track.position onJamTrackPlayerStoreChanged: (changes) -> #logger.debug("PopupMediaControls: jamtrack changed", changes) @@ -222,9 +227,16 @@ mixins.push(Reflux.listenTo(JamTrackPlayerStore, 'onJamTrackPlayerStoreChanged') active = jamTrack.last_stem_id? trackOptions = [] - for track in jamTrack.tracks - if track.track_type == 'Track' + jamTrack.tracks.sort((a, b) => + aWeight = @computeWeight(a) + bWeight = @computeWeight(b) + return aWeight - bWeight + ) + + + for track in jamTrack.tracks + if track.track_type == 'Track' || track.track_type == 'Click' if track.instrument instrumentId = track.instrument.id @@ -239,7 +251,6 @@ mixins.push(Reflux.listenTo(JamTrackPlayerStore, 'onJamTrackPlayerStoreChanged') boundStemPlayClick = this.downloadStem.bind(this) boundStemChange = this.stemChanged.bind(this) - console.log("jamTrack.lastStemId", jamTrack.last_stem_id) myMixdowns.push `
    @@ -267,8 +278,12 @@ mixins.push(Reflux.listenTo(JamTrackPlayerStore, 'onJamTrackPlayerStoreChanged') tracks = [] if jamTrack? for track in jamTrack.tracks - if track.track_type == 'Track' - if track.instrument + if track.track_type == 'Track' || track.track_type == 'Click' + if track.track_type == 'Click' + instrumentId = track.instrument.id + instrumentDescription = 'Clicktrack' + part = '' + else if track.instrument instrumentId = track.instrument.id instrumentDescription = track.instrument.description if track.part? && track.part != instrumentDescription @@ -280,9 +295,17 @@ mixins.push(Reflux.listenTo(JamTrackPlayerStore, 'onJamTrackPlayerStoreChanged') tracks.push(` {instrumentDescription} {part} - + `) + if jamTrack?.jmep?.Events? && jamTrack.jmep.Events[0].metronome? + # tap-in detected; show user tap-in option + tracks.push(` + + Count-in + + `) + stems = `
    @@ -632,6 +655,7 @@ mixins.push(Reflux.listenTo(JamTrackPlayerStore, 'onJamTrackPlayerStoreChanged') else pitch = parseInt(pitch) + count_in = false # get mute state of all tracks $mutes = $(@getDOMNode()).find('.stems .stem .stem-mute') @@ -642,10 +666,13 @@ mixins.push(Reflux.listenTo(JamTrackPlayerStore, 'onJamTrackPlayerStoreChanged') stemId = $mute.attr('data-stem-id') muted = $mute.is(':checked') - tracks.push({id: stemId, mute: muted}) + if stemId == 'count-in' + count_in = !muted + else + tracks.push({id: stemId, mute: muted}) ) - mixdown = {jamTrackID: @state.jamTrackState.jamTrack.id, name: name, settings: {speed:speed, pitch: pitch, tracks:tracks}} + mixdown = {jamTrackID: @state.jamTrackState.jamTrack.id, name: name, settings: {speed:speed, pitch: pitch, "count-in": count_in, tracks:tracks}} JamTrackPlayerActions.createMixdown(mixdown, @createMixdownDone, @createMixdownFail) diff --git a/web/app/assets/javascripts/react-components/PopupMediaControls.js.jsx.coffee b/web/app/assets/javascripts/react-components/PopupMediaControls.js.jsx.coffee index 4c37b8a51..f3d90dcd7 100644 --- a/web/app/assets/javascripts/react-components/PopupMediaControls.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/PopupMediaControls.js.jsx.coffee @@ -248,7 +248,7 @@ mixins.push(Reflux.listenTo(JamTrackStore, 'onJamTrackChanged')) trackOptions = [] for track in jamTrack.tracks - if track.track_type == 'Track' + if track.track_type == 'Track' || track.track_type == 'Click' if track.instrument instrumentId = track.instrument.id diff --git a/web/app/assets/javascripts/react-components/SessionMediaTracks.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionMediaTracks.js.jsx.coffee index ca7012871..c214aab20 100644 --- a/web/app/assets/javascripts/react-components/SessionMediaTracks.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/SessionMediaTracks.js.jsx.coffee @@ -276,8 +276,7 @@ ChannelGroupIds = context.JK.ChannelGroupIds # All the JamTracks mediaTracks.push(``) - # show metronome only if it's a full jamtrack - if @state.metronome? && @state.jamTrackMixdown.id == null + if @state.metronome? # && @state.jamTrackMixdown.id == null @state.metronome.mode = MIX_MODES.PERSONAL mediaTracks.push(``) diff --git a/web/app/assets/javascripts/react-components/actions/ConfigureTracksActions.js.coffee b/web/app/assets/javascripts/react-components/actions/ConfigureTracksActions.js.coffee new file mode 100644 index 000000000..4a2890385 --- /dev/null +++ b/web/app/assets/javascripts/react-components/actions/ConfigureTracksActions.js.coffee @@ -0,0 +1,22 @@ +context = window + +@ConfigureTracksActions = Reflux.createActions({ + reset: {} + trySave: {} + midiScan: {} + vstScan: {} + vstScanComplete: {} + clearVsts: {} + cancelEdit: {} + deleteTrack: {} + updateOutputs: {} + showAddNewTrack: {} + showEditTrack: {} + showEditOutputs: {} + showVstSettings: {} + associateInputsWithTrack: {} + associateInstrumentWithTrack: {} + associateVSTWithTrack: {} + associateMIDIWithTrack: {} + desiredTrackType: {} +}) diff --git a/web/app/assets/javascripts/react-components/actions/UserActions.js.coffee b/web/app/assets/javascripts/react-components/actions/UserActions.js.coffee index c9469e5d6..daf5708ab 100644 --- a/web/app/assets/javascripts/react-components/actions/UserActions.js.coffee +++ b/web/app/assets/javascripts/react-components/actions/UserActions.js.coffee @@ -2,5 +2,6 @@ context = window @UserActions = Reflux.createActions({ loaded: {} + modify: {} }) diff --git a/web/app/assets/javascripts/react-components/helpers/MixerHelper.js.coffee b/web/app/assets/javascripts/react-components/helpers/MixerHelper.js.coffee index 8fc110780..642b79bea 100644 --- a/web/app/assets/javascripts/react-components/helpers/MixerHelper.js.coffee +++ b/web/app/assets/javascripts/react-components/helpers/MixerHelper.js.coffee @@ -402,6 +402,9 @@ MIX_MODES = context.JK.MIX_MODES; else trackName = instrumentName + if jamTrack.track_type == 'Click' + trackName = 'Clicktrack' + data = name: jamTrackName trackName: trackName diff --git a/web/app/assets/javascripts/react-components/helpers/SessionHelper.js.coffee b/web/app/assets/javascripts/react-components/helpers/SessionHelper.js.coffee index e9d88c04a..460aa98e8 100644 --- a/web/app/assets/javascripts/react-components/helpers/SessionHelper.js.coffee +++ b/web/app/assets/javascripts/react-components/helpers/SessionHelper.js.coffee @@ -70,7 +70,7 @@ context = window jamTracks: () -> if @session && @session.jam_track @session.jam_track.tracks.filter((track)-> - track.track_type == 'Track' + track.track_type == 'Track' || track.track_type == 'Click' ) else null diff --git a/web/app/assets/javascripts/react-components/landing/GiftCardLandingPage.js.jsx.coffee b/web/app/assets/javascripts/react-components/landing/GiftCardLandingPage.js.jsx.coffee new file mode 100644 index 000000000..8b0c5ada5 --- /dev/null +++ b/web/app/assets/javascripts/react-components/landing/GiftCardLandingPage.js.jsx.coffee @@ -0,0 +1,94 @@ +context = window +rest = context.JK.Rest() + +@GiftCardLandingPage = React.createClass({ + + render: () -> + + if this.state.done + ctaButtonText10 = 'sending you in...' + ctaButtonText20 = 'sending you in...' + else if this.state.processing + ctaButtonText10 = 'hold on...' + ctaButtonText20 = 'hold on...' + else + ctaButtonText10 = `ADD $10 CARD
    TO CART
    ` + ctaButtonText20 = `ADD $20 CARD
    TO CART
    ` + + + ctaButtons = + `
    + + +
    ` + + + `
    +
    +
    + gift card +

    $10 or $20 JAMTRACKS GIFT CARDS

    +

    A PERFECT GIFT FOR THE HOLIDAYS

    +
    +
    +
    + + +
    + Preview A JamTrack +
    "{this.props.jam_track.name}"
    +
    +
    +

    Click the play buttons below to preview the master mix and 20-second samples of all the isolated tracks.

    +
    + +
    +

    + Get a $10 gift card (good for 5 songs) or a $20 gift card (good for 10 songs), and your happy + gift card getter can choose their favorites from our catalog of 3,700+ popular songs. +

    + {ctaButtons} + or browse our catalog of 3,700+ songs +
    +
    +
    +
    +

    + JamTracks by JamKazam are the best way to play along with your favorite songs. Far better and different than traditional + backing tracks, our JamTracks are complete multi-track professional recordings, with fully isolated tracks for each part of the music. + And our free app and Internet service are packed with features that give you unmatched creative freedom to learn, practice, record, play with others, and share your performances. +

    +
    +
    ` + + getInitialState: () -> + {processing:false} + + componentDidMount:() -> + $root = $(this.getDOMNode()) + +# add item to cart, create the user if necessary, and then place the order to get the free JamTrack. + ctaClick: (card_type, e) -> + e.preventDefault() + + return if @state.processing + + loggedIn = context.JK.currentUserId? + + rest.addGiftCardToShoppingCart({id: card_type}).done((response) => + + if loggedIn + @setState({done: true}) + context.location = '/client#/shoppingCart' + else + @setState({done: true}) + context.location = '/client#/shoppingCart' + + ).fail((jqXHR, textStatus, errorMessage) => + if jqXHR.status == 422 + errors = JSON.parse(jqXHR.responseText) + cart_errors = errors?.errors?.cart_id + context.JK.app.ajaxError(jqXHR, textStatus, errorMessage) + @setState({processing:false}) + ) +}) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/landing/JamTrackLandingPage.js.jsx.coffee b/web/app/assets/javascripts/react-components/landing/JamTrackLandingPage.js.jsx.coffee index a1e01f323..0150f24e9 100644 --- a/web/app/assets/javascripts/react-components/landing/JamTrackLandingPage.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/landing/JamTrackLandingPage.js.jsx.coffee @@ -119,10 +119,9 @@ rest = context.JK.Rest() isFree = context.JK.currentUserFreeJamTrack - rest.addJamtrackToShoppingCart({id: @props.jam_track.id}).done((response) => + rest.addJamtrackToShoppingCart({id: @props.jam_track.id, clear:true}).done((response) => if isFree if loggedIn - context.JK.currentUserFreeJamTrack = true # make sure the user sees no more free notices @setState({done: true}) context.location = '/client?redeemed_flow=1#/jamtrack' else diff --git a/web/app/assets/javascripts/react-components/landing/RedeemGiftCardPage.js.jsx.coffee b/web/app/assets/javascripts/react-components/landing/RedeemGiftCardPage.js.jsx.coffee new file mode 100644 index 000000000..8bf7b2025 --- /dev/null +++ b/web/app/assets/javascripts/react-components/landing/RedeemGiftCardPage.js.jsx.coffee @@ -0,0 +1,159 @@ +context = window +rest = context.JK.Rest() +ReactCSSTransitionGroup = React.addons.CSSTransitionGroup; + +@RedeemGiftCardPage = React.createClass({ + + render: () -> + + + if this.state.formErrors? + for key, value of this.state.formErrors + break + + errorText = context.JK.getFullFirstError(key, @state.formErrors, {email: 'Email', password: 'Password', gift_card: 'Gift Card Code', 'terms_of_service' : 'The terms of service'}) + + buttonClassnames = classNames({'redeem-giftcard': true, 'button-orange': true, disabled: @state.processing || @state.done }) + + if @state.done + button = + `
    +
    You have {this.state.gifted_jamtracks} free JamTracks on your account!
    +
    You can now browse our collection and redeem them.
    +
    ` + else + button = `` + + action = ` + {button} + ` + + + if context.JK.currentUserId? + form = + `
    + + {action} + ` + instruments = `

    Enter the code from the back of your gift card to associate it with your account.

    ` + else + form = + `
    + + + +
    + +
    + {action} + ` + instruments = `

    Enter the code from the back of your gift card to associate it with your new JamKazam account.

    ` + + + classes = classNames({'redeem-container': true, 'not-logged-in': !context.JK.currentUserId?, 'logged-in': context.JK.currentUserId? }) + `
    +
    +

    Redeem Your Gift Card

    + {instruments} + {form} +
    + {errorText} +
    +
    +
    ` + + getInitialState: () -> + {formErrors: null, processing:false, gifted_jamtracks: null} + + privacyPolicy: (e) -> + e.preventDefault() + + context.JK.popExternalLink('/corp/privacy') + + termsClicked: (e) -> + e.preventDefault() + + context.JK.popExternalLink('/corp/terms') + + componentDidMount:() -> + $root = $(this.getDOMNode()) + $checkbox = $root.find('.terms-checkbox') + console.log("$checkbox", $checkbox) + context.JK.checkbox($checkbox) + + submit: (e) -> + @action(e) + action: (e) -> + + if @state.done || @state.processing + e.preventDefault() + return + + if context.JK.currentUserId? + @redeem(e) + else + @signup(e) + + redeem: (e) -> + e.preventDefault() + return if @state.done || @state.processing + + $root = $(@getDOMNode()) + $code = $root.find('input[name="code"]') + code = $code.val() + + rest.redeemGiftCard({gift_card: code}) + .done((response) => + + @setState({formErrors: null, processing:false, done: true, gifted_jamtracks: response.gifted_jamtracks}) + + ).fail((jqXHR) => + @setState({processing:false}) + + if jqXHR.status == 422 + response = JSON.parse(jqXHR.responseText) + if response.errors + @setState({formErrors: response.errors}) + else + context.JK.app.notify({title: 'Unknown Error', text: jqXHR.responseText}) + else + context.JK.app.notifyServerError(jqXHR, "Unable to Redeem Giftcard") + ) + + signup: (e) -> + e.preventDefault() + + return if @state.done || @state.processing + + $root = $(@getDOMNode()) + $email = $root.find('input[name="email"]') + $code = $root.find('input[name="code"]') + $password = $root.find('input[name="password"]') + terms = $root.find('input[name="terms"]').is(':checked') + + @setState({processing:true}) + email = $email.val() + password = $password.val() + code = $code.val() + if !code + # must pass up non-null value to indicate user is trying to redeem giftcard while creating account + code = '' + + rest.signup({email: email, password: password, gift_card: code, terms: terms}) + .done((response) => + + @setState({formErrors: null, processing:false, done: true, gifted_jamtracks: response.gifted_jamtracks}) + + ).fail((jqXHR) => + @setState({processing:false}) + + if jqXHR.status == 422 + response = JSON.parse(jqXHR.responseText) + if response.errors + @setState({formErrors: response.errors}) + else + context.JK.app.notify({title: 'Unknown Signup Error', text: jqXHR.responseText}) + else + context.JK.app.notifyServerError(jqXHR, "Unable to Sign Up") + ) +}) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/stores/JamTrackStore.js.coffee b/web/app/assets/javascripts/react-components/stores/JamTrackStore.js.coffee index 86d4ee8e6..46a095bb8 100644 --- a/web/app/assets/javascripts/react-components/stores/JamTrackStore.js.coffee +++ b/web/app/assets/javascripts/react-components/stores/JamTrackStore.js.coffee @@ -135,7 +135,7 @@ JamTrackActions = @JamTrackActions @trackDetail = context.jamClient.JamTrackGetTrackDetail (fqId) if @trackDetail.version? - logger.error("after invalidating package, the version is still wrong!") + logger.error("after invalidating package, the version is still wrong!", @trackDetail.version) throw "after invalidating package, the version is still wrong!" if @jamTrack.activeMixdown.client_state == 'cant_open' @@ -165,6 +165,20 @@ JamTrackActions = @JamTrackActions # JamTrackPlay means 'load' logger.debug("JamTrackStore: loading mixdown") context.jamClient.JamTrackStopPlay(); + + if @jamTrack.jmep + + if @jamTrack.activeMixdown.settings.speed? + @jamTrack.jmep.speed = @jamTrack.activeMixdown.settings.speed + else + @jamTrack.jmep.speed = 0 + + logger.debug("setting jmep data. speed:" + @jamTrack.jmep.speed) + + context.jamClient.JamTrackLoadJmep(fqId, @jamTrack.jmep) + else + logger.debug("no jmep data for jamtrack") + result = context.jamClient.JamTrackPlay(fqId); if !result @jamTrack.activeMixdown.client_state = 'cant_open' diff --git a/web/app/assets/javascripts/react-components/stores/UserStore.js.coffee b/web/app/assets/javascripts/react-components/stores/UserStore.js.coffee index 133c3f87c..c92bbe9d7 100644 --- a/web/app/assets/javascripts/react-components/stores/UserStore.js.coffee +++ b/web/app/assets/javascripts/react-components/stores/UserStore.js.coffee @@ -8,10 +8,24 @@ logger = context.JK.logger listenables: @UserActions + init: -> + this.listenTo(context.AppStore, this.onAppInit) + + onAppInit: (@app) -> + @loadAnonymousUser() + + loadAnonymousUser: () -> + @user = {id: null, has_redeemable_jamtrack: context.JK.currentUserFreeJamTrack, purchased_jamtracks_count:0, show_free_jamtrack: context.JK.currentUserFreeJamTrack } + @changed() + onLoaded:(user) -> @user = user @changed() + onModify: (changes) -> + @user = $.extend({}, @user, changes) + @changed() + changed:() -> @trigger({user: @user}) } diff --git a/web/app/assets/javascripts/react-components/stores/VideoStore.js.coffee b/web/app/assets/javascripts/react-components/stores/VideoStore.js.coffee index 3cca5720e..f067f19ff 100644 --- a/web/app/assets/javascripts/react-components/stores/VideoStore.js.coffee +++ b/web/app/assets/javascripts/react-components/stores/VideoStore.js.coffee @@ -16,7 +16,6 @@ BackendToFrontend = { } BackendToFrontendFPS = { - 0: 30, 1: 24, 2: 20, @@ -140,6 +139,11 @@ BackendToFrontendFPS = { @state.videoShared = @videoShared this.trigger(@state) + onBringVideoToFront: -> + if @videoShared + @logger.debug("BringVideoToFront") + context.jamClient.BringVideoWindowToFront(); + onTestVideo: () -> return unless context.jamClient.testVideoRender? @@ -150,7 +154,7 @@ BackendToFrontendFPS = { onToggleVideo: () -> if @videoShared - @onStopVideo() + @onBringVideoToFront() else @onStartVideo() diff --git a/web/app/assets/javascripts/recordingModel.js b/web/app/assets/javascripts/recordingModel.js index 57d9eaf7c..61114fdb8 100644 --- a/web/app/assets/javascripts/recordingModel.js +++ b/web/app/assets/javascripts/recordingModel.js @@ -273,6 +273,7 @@ function handleRecordingStopped(recordingId, result) { if(recordingId == "video") { + // comes from VideoRecordingStopped return; } @@ -324,6 +325,16 @@ function handleRecordingAborted(recordingId, result) { if(recordingId == "video") { + + // comes from AbortedVideoRecording + recordingId = result; + if (arguments.length == 2) { + result = arguments[2] + } + logger.debug("video recording aborted", result) + context.JK.Banner.showAlert("Video has stopped recording. Audio is still recording.") + //context.RecordingActions.stopRecording() + return; } diff --git a/web/app/assets/javascripts/redeem_complete.js b/web/app/assets/javascripts/redeem_complete.js index 0b89e1c9a..d5bb834e2 100644 --- a/web/app/assets/javascripts/redeem_complete.js +++ b/web/app/assets/javascripts/redeem_complete.js @@ -31,11 +31,6 @@ function beforeShow() { - - } - - function afterShow(data) { - context.JK.Tracking.redeemCompleteTrack() $noPurchasesPrompt.addClass('hidden') @@ -44,6 +39,10 @@ $purchasedJamTrackHeader.attr('status', 'in-progress') $jamTrackInBrowser.addClass('hidden') $jamTrackInClient.addClass('hidden') + } + + function afterShow(data) { + // if there is no current user, but it apperas we have a login cookie, just refresh @@ -59,23 +58,19 @@ function handleShoppingCartResponse(carts) { - if(!checkoutUtils.hasOneFreeItemInShoppingCart(carts)) { + if(!checkoutUtils.hasOnlyFreeItemsInShoppingCart(carts)) { // the user has multiple items in their shopping cart. They shouldn't be here. logger.error("invalid access of redeemComplete page") window.location = '/client#/jamtrack/search' } else { - // ok, we have one, free item. save it for - shoppingCartItem = carts[0]; rest.placeOrder() .done(function(purchaseResponse) { - context.JK.currentUserFreeJamTrack = false // make sure the user sees no more free notices without having to do a full page refresh rest.updateUser() checkoutUtils.setLastPurchase(purchaseResponse) jamTrackUtils.checkShoppingCart() - //app.refreshUser() // this only causes grief in tests for some reason, and with currentUserFreeJamTrack = false above, this is probably now unnecessary prepThanks(); }) diff --git a/web/app/assets/javascripts/redeem_signup.js b/web/app/assets/javascripts/redeem_signup.js index d3d514258..159794260 100644 --- a/web/app/assets/javascripts/redeem_signup.js +++ b/web/app/assets/javascripts/redeem_signup.js @@ -73,7 +73,7 @@ else if(carts.length > 1) { // the user has multiple items in their shopping cart. They shouldn't be here. logger.error("invalid access of redeemJamTrack page; multiple") - window.location = '/client#/jamtrack/search' + window.location = '/client#/shoppingCart' } else { var item = carts[0]; diff --git a/web/app/assets/javascripts/shopping_cart.js b/web/app/assets/javascripts/shopping_cart.js index 9193d9735..c0589b173 100644 --- a/web/app/assets/javascripts/shopping_cart.js +++ b/web/app/assets/javascripts/shopping_cart.js @@ -9,6 +9,7 @@ var $screen = null; var $content = null; + var totalCost = 0; function beforeShow(data) { clearContent(); @@ -30,7 +31,7 @@ function proceedCheckout(e) { e.preventDefault(); - if (context.JK.currentUserFreeJamTrack) { + if (totalCost == 0) { if(context.JK.currentUserId) { logger.debug("proceeding to redeem complete screen because user has a free jamtrack and is logged in") window.location = '/client#/redeemComplete' @@ -120,6 +121,8 @@ }); data.sub_total = sub_total; + totalCost = data.sub_total; + data.carts = carts; var $cartsHtml = $( context._.template( diff --git a/web/app/assets/javascripts/web/individual_jamtrack.js b/web/app/assets/javascripts/web/individual_jamtrack.js index 94fba4ebd..c900247e1 100644 --- a/web/app/assets/javascripts/web/individual_jamtrack.js +++ b/web/app/assets/javascripts/web/individual_jamtrack.js @@ -41,6 +41,10 @@ context._.each(jam_track.tracks, function (track) { + if (track.track_type == 'Click') { + return; + } + var $element = $('
    ') $previews.append($element); diff --git a/web/app/assets/javascripts/web/individual_jamtrack_band_v1.js b/web/app/assets/javascripts/web/individual_jamtrack_band_v1.js index 25b5620b1..6a6e2be7e 100644 --- a/web/app/assets/javascripts/web/individual_jamtrack_band_v1.js +++ b/web/app/assets/javascripts/web/individual_jamtrack_band_v1.js @@ -30,6 +30,10 @@ context._.each(jam_track.tracks, function (track) { + if (track.track_type == 'Click') { + return; + } + var $element = $('
    ') $previews.append($element); diff --git a/web/app/assets/javascripts/web/individual_jamtrack_v1.js b/web/app/assets/javascripts/web/individual_jamtrack_v1.js index c6d3e4dff..a4288fa77 100644 --- a/web/app/assets/javascripts/web/individual_jamtrack_v1.js +++ b/web/app/assets/javascripts/web/individual_jamtrack_v1.js @@ -40,6 +40,10 @@ context._.each(jam_track.tracks, function (track) { + if (track.track_type == 'Click') { + return; + } + var $element = $('
    ') $previews.append($element); diff --git a/web/app/assets/stylesheets/client/checkout_complete.css.scss b/web/app/assets/stylesheets/client/checkout_complete.css.scss index 45c9b3951..c3797f649 100644 --- a/web/app/assets/stylesheets/client/checkout_complete.css.scss +++ b/web/app/assets/stylesheets/client/checkout_complete.css.scss @@ -73,6 +73,10 @@ } } } + .thanks-detail.gift-card, .thanks-detail.jam-tracks-in-browser{ + margin-top: 20px; + } + .thanks-detail.purchased-jam-track { margin-top:20px; diff --git a/web/app/assets/stylesheets/client/jamkazam.css.scss b/web/app/assets/stylesheets/client/jamkazam.css.scss index 6af987666..292e6c631 100644 --- a/web/app/assets/stylesheets/client/jamkazam.css.scss +++ b/web/app/assets/stylesheets/client/jamkazam.css.scss @@ -702,4 +702,23 @@ $ReactSelectVerticalPadding: 3px; .Select-search-prompt { padding:3px 0 !important; -} \ No newline at end of file +} + + +.session-track-list-enter { + opacity: 0.01; + transition: opacity .5s ease-in; + + &.session-track-list-enter-active { + opacity: 1; + } +} + +.session-track-list-leave { + opacity:1; + transition: opacity .5s ease-in; + + &.session-track-list-leave-active { + opacity: 0.01; + } +} diff --git a/web/app/assets/stylesheets/client/react-components/SessionScreen.css.scss b/web/app/assets/stylesheets/client/react-components/SessionScreen.css.scss index e7e510b2e..bea31605f 100644 --- a/web/app/assets/stylesheets/client/react-components/SessionScreen.css.scss +++ b/web/app/assets/stylesheets/client/react-components/SessionScreen.css.scss @@ -386,22 +386,3 @@ $session-screen-divider: 1190px; } } } - - -.session-track-list-enter { - opacity: 0.01; - transition: opacity .5s ease-in; - - &.session-track-list-enter-active { - opacity: 1; - } -} - -.session-track-list-leave { - opacity:1; - transition: opacity .5s ease-in; - - &.session-track-list-leave-active { - opacity: 0.01; - } -} diff --git a/web/app/assets/stylesheets/client/redeem_complete.css.scss b/web/app/assets/stylesheets/client/redeem_complete.css.scss index 617e1a01f..82eff74e2 100644 --- a/web/app/assets/stylesheets/client/redeem_complete.css.scss +++ b/web/app/assets/stylesheets/client/redeem_complete.css.scss @@ -74,7 +74,7 @@ display:inline-block; } - .download-jamkazam-wrapper { + .download-jamkazam-wrapper, .back-to-browsing { text-align:center; display:block; margin-top:35px; @@ -83,6 +83,7 @@ display:none; } } + .thanks-detail.purchased-jam-track { margin-top:20px; diff --git a/web/app/assets/stylesheets/landings/individual_jamtrack.css.scss b/web/app/assets/stylesheets/landings/individual_jamtrack.css.scss index c54f8bd2f..6ecc65a17 100644 --- a/web/app/assets/stylesheets/landings/individual_jamtrack.css.scss +++ b/web/app/assets/stylesheets/landings/individual_jamtrack.css.scss @@ -162,6 +162,18 @@ body.web.individual_jamtrack { text-align: center; } + img.gift-card-preview { + width:300px; + float: left; + margin-left: -15px; // because image has black on the left, which you can't see on back background + margin-right: 20px; + margin-bottom: 20px; + } + p.gift-getter { + margin-top:20px; + line-height:125%; + } + img.app-preview { width: 340px; float: left; @@ -188,6 +200,7 @@ body.web.individual_jamtrack { } .cta-button { + cursor:pointer; background-color: $cta-color; &.processing { @@ -205,6 +218,9 @@ body.web.individual_jamtrack { .browse-all { color: #ffb800; + text-decoration: underline; + text-align: center; + display: block; } p { @@ -313,6 +329,16 @@ body.web.individual_jamtrack { border-width: 0 0 $chunkyBorderWidth; border-style: solid; border-color: $copy-color-on-dark; + + &.gift-card { + padding:20px 0 10px; + .jamtrack-title { + margin-top:10px; + font-size:18px; + font-style:italic; + } + } + } .preview-area { @@ -320,6 +346,11 @@ body.web.individual_jamtrack { padding: 10px; border-width: 0 0 $chunkyBorderWidth; + + &.gift-card { + border-width: 0 0 2px; + } + border-style: solid; border-color: $copy-color-on-dark; @@ -338,6 +369,9 @@ body.web.individual_jamtrack { margin-bottom:10px; } + .cta-buttons { + text-align:center; + } .cta-button { font-size: 24px; color: white; @@ -348,6 +382,13 @@ body.web.individual_jamtrack { width: 100%; border: 1px outset buttonface; font-family: Raleway, Arial, Helvetica, sans-serif; + + &.gift-card { + font-size:16px; + width:138px; + margin:15px 5px; + display:inline-block; + } } } @@ -385,12 +426,7 @@ body.web.individual_jamtrack { border: 1px outset buttonface; font-family: Raleway, Arial, Helvetica, sans-serif; } - .browse-all { - text-decoration: underline; - text-align: center; - display: block; - } .privacy-policy { text-decoration: underline; } diff --git a/web/app/assets/stylesheets/landings/redeem_giftcard.css.scss b/web/app/assets/stylesheets/landings/redeem_giftcard.css.scss new file mode 100644 index 000000000..c1cd5af24 --- /dev/null +++ b/web/app/assets/stylesheets/landings/redeem_giftcard.css.scss @@ -0,0 +1,90 @@ +@import "client/common"; + +body.web.redeem_giftcard { + + h2 { + margin-bottom:20px; + } + + label{ + margin-bottom:4px; + color:$ColorTextTypical; + } + + input{ + margin-bottom:20px; + width:200px; + } + + .redeem-container { + margin-left:350px; + width:400px; + padding-top:20px; + + &.logged-in { + button { + margin-top:10px !important; + } + } + + &.not-logged-in { + + } + } + .redeem-content { + + } + p.instructions { + line-height:125%; + color:$ColorTextTypical; + margin-bottom:20px; + } + + button { + display:block !important; + height: 29px !important; + margin-bottom: 10px; + margin-right: 0px; + font-size: 16px !important; + padding: 7px 3px !important; + line-height:inherit !important; + margin-left:2px !important; + margin-top:15px; + } + + .icheckbox_minimal { + float: left; + top: -2px; + margin-left: 0; + margin-right:10px; + } + + .errors { + font-size:14px; + height:20px; + margin:0; + visibility: hidden; + color: red; + font-weight: bold; + + &.active { + visibility: visible; + } + } + + form { + margin-bottom:20px; + } + .terms-help { + float:left; + margin-top:-5px; + font-size:12px; + width:178px; + } + + .done-action { + margin-top: 20px; + line-height: 125%; + } + +} \ No newline at end of file diff --git a/web/app/controllers/api_jam_tracks_controller.rb b/web/app/controllers/api_jam_tracks_controller.rb index e8de18f44..86af55c28 100644 --- a/web/app/controllers/api_jam_tracks_controller.rb +++ b/web/app/controllers/api_jam_tracks_controller.rb @@ -3,7 +3,7 @@ class ApiJamTracksController < ApiController # have to be signed in currently to see this screen before_filter :api_signed_in_user, :except => [:index, :autocomplete, :show_with_artist_info, :artist_index] before_filter :api_any_user, :only => [:index, :autocomplete, :show_with_artist_info, :artist_index] - before_filter :lookup_jam_track_right, :only => [:download,:enqueue, :show_jam_track_right, :mark_active, :download_stem] + before_filter :lookup_jam_track_right, :only => [:download, :enqueue, :show_jam_track_right, :mark_active, :download_stem] before_filter :ip_blacklist, :only => [:download_stem, :download] before_filter :user_blacklist, :only => [:download_stem, :download] @@ -285,6 +285,25 @@ class ApiJamTracksController < ApiController puts "jamtrack_mixdowns #{jamtrack_mixdowns}" end + def ios_order_placed + jam_track = JamTrack.find(params[:jam_track_id]) + + sales = Sale.ios_purchase(current_user, jam_track, nil) + + # + # sales.each do |sale| + # if sale.is_jam_track_sale? + # sale.sale_line_items.each do |line_item| + # jam_track = line_item.product + # jam_track_right = jam_track.right_for_user(current_user) + # response[:jam_tracks] << {name: jam_track.name, id: jam_track.id, jam_track_right_id: jam_track_right.id, version: jam_track.version} + # end + # end + # end + response = {name: jam_track.name, id: jam_track.id, jam_track_right_id: jam_track.right_for_user(current_user).id, version: jam_track.version} + render :json => response, :status => 200 + end + private def lookup_jam_track_right @jam_track_right = JamTrackRight.where("jam_track_id=? AND user_id=?", params[:id], current_user.id).first diff --git a/web/app/controllers/api_recurly_controller.rb b/web/app/controllers/api_recurly_controller.rb index a76113739..1da2f62e0 100644 --- a/web/app/controllers/api_recurly_controller.rb +++ b/web/app/controllers/api_recurly_controller.rb @@ -124,16 +124,28 @@ class ApiRecurlyController < ApiController def place_order error=nil - response = {jam_tracks: []} + response = {jam_tracks: [], gift_cards: []} + + if Sale.is_mixed(current_user.shopping_carts) + msg = "has free and non-free items. Try removing non-free items." + render json: {message: "Cart " + msg, errors: {cart: [msg]}}, :status => 404 + return + end sales = Sale.place_order(current_user, current_user.shopping_carts) + sales.each do |sale| - if sale.is_jam_track_sale? - sale.sale_line_items.each do |line_item| + sale.sale_line_items.each do |line_item| + if line_item.is_jam_track? jam_track = line_item.product jam_track_right = jam_track.right_for_user(current_user) response[:jam_tracks] << {name: jam_track.name, id: jam_track.id, jam_track_right_id: jam_track_right.id, version: jam_track.version} + elsif line_item.is_gift_card? + gift_card = line_item.product + response[:gift_cards] << {name: gift_card.name, id: gift_card.id} + else + raise 'unknown sale line item type: ' + line_item.product_type end end end diff --git a/web/app/controllers/api_shopping_carts_controller.rb b/web/app/controllers/api_shopping_carts_controller.rb index 05bf66aeb..e45429f86 100644 --- a/web/app/controllers/api_shopping_carts_controller.rb +++ b/web/app/controllers/api_shopping_carts_controller.rb @@ -20,13 +20,41 @@ class ApiShoppingCartsController < ApiController raise StateError, "Invalid JamTrack." end - @cart = ShoppingCart.add_jam_track_to_cart(any_user, jam_track) + @cart = ShoppingCart.add_jam_track_to_cart(any_user, jam_track, clear:params[:clear]) if @cart.errors.any? response.status = :unprocessable_entity respond_with @cart else - respond_with @cart, responder: ApiResponder, :status => 201 + # let add_jamtrack.rabl take over + end + end + + def add_gift_card + gift_card_type = nil + + id = params[:id] + + if id && id.to_i == 5 + gift_card_type = 'jam_tracks_5' + elsif id && id.to_i == 10 + gift_card_type = 'jam_tracks_10' + end + + gift_card = GiftCardType.find_by_id(gift_card_type) + + # verify GiftCard exists + if gift_card.nil? + raise StateError, "Invalid JamTrack." + end + + @cart = ShoppingCart.add_item_to_cart(any_user, gift_card) + + if @cart.errors.any? + response.status = :unprocessable_entity + respond_with @cart + else + # let add_gift_card.rabl take over end end @@ -42,7 +70,7 @@ class ApiShoppingCartsController < ApiController response.statue = :unprocessable_entity respond_with @cart else - respond_with @cart, responder: ApiResponder, :status => 200 + # let update_cart.rabl take over end end @@ -52,7 +80,7 @@ class ApiShoppingCartsController < ApiController ShoppingCart.remove_jam_track_from_cart(any_user, @cart) - respond_with responder: ApiResponder, :status => 204 + # let remove_cart.rabl take over end # take all shopping carts from anonymous user and copy them to logged in user diff --git a/web/app/controllers/api_users_controller.rb b/web/app/controllers/api_users_controller.rb index a4eaa6c5c..c267e23bd 100644 --- a/web/app/controllers/api_users_controller.rb +++ b/web/app/controllers/api_users_controller.rb @@ -14,8 +14,8 @@ ApiUsersController < ApiController :band_invitation_index, :band_invitation_show, :band_invitation_update, # band invitations :set_password, :begin_update_email, :update_avatar, :delete_avatar, :generate_filepicker_policy, :share_session, :share_recording, - :affiliate_report, :audio_latency, :broadcast_notification] - before_filter :ip_blacklist, :only => [:create] + :affiliate_report, :audio_latency, :broadcast_notification, :redeem_giftcard] + before_filter :ip_blacklist, :only => [:create, :redeem_giftcard] respond_to :json, :except => :calendar respond_to :ics, :only => :calendar @@ -81,6 +81,7 @@ ApiUsersController < ApiController terms_of_service: params[:terms_of_service].to_i, location: {:country => nil, :state => nil, :city => nil}, signup_hint: signup_hint, + gift_card: params[:gift_card], affiliate_referral_id: cookies[:affiliate_visitor] } @@ -598,6 +599,7 @@ ApiUsersController < ApiController @dump.user_id = params[:user_id] @dump.session_id = params[:session_id] @dump.timestamp = params[:timestamp] + @dump.description = params[:description] unless @dump.save # There are at least some conditions on valid dumps (need client_type) @@ -919,6 +921,45 @@ ApiUsersController < ApiController .find(params[:id]) end + def redeem_giftcard + @gift_card = GiftCard.find_by_code(params[:gift_card]) + + if @gift_card.nil? + render json: {errors:{gift_card: ['does not exist']}}, status: 422 + return + end + + if current_user.gift_cards.count >= 5 + render json: {errors:{gift_card: ['has too many on account']}}, status: 422 + return + end + + if @gift_card.user + if @gift_card.user == current_user + render json: {errors:{gift_card: ['already redeemed by you']}}, status: 422 + return + else + render json: {errors:{gift_card: ['already redeemed by another']}}, status: 422 + return + end + end + + @gift_card.user = current_user + @gift_card.save + + if @gift_card.errors.any? + respond_with_model(@gift_card) + return + else + + # apply gift card items to everything in shopping cart + current_user.reload + ShoppingCart.apply_gifted_jamtracks(current_user) + render json: {gifted_jamtracks:current_user.gifted_jamtracks}, status: 200 + end + end + + ###################### RECORDINGS ####################### # def recording_index # @recordings = User.recording_index(current_user, params[:id]) diff --git a/web/app/controllers/landings_controller.rb b/web/app/controllers/landings_controller.rb index fa13b018f..e566b6adb 100644 --- a/web/app/controllers/landings_controller.rb +++ b/web/app/controllers/landings_controller.rb @@ -208,5 +208,33 @@ class LandingsController < ApplicationController render 'affiliate_program', layout: 'web' end + def redeem_giftcard + @no_landing_tag = true + @landing_tag_play_learn_earn = true + render 'redeem_giftcard', layout: 'web' + end + + def buy_gift_card + @no_landing_tag = true + @landing_tag_play_learn_earn = true + @show_after_black_bar_border = true + @jam_track = JamTrack.find_by_slug('elton-john-rocket-man') + @jam_track = JamTrack.first unless @jam_track + + instrument_id = nil + instrument_name = nil + instrument_count = 0 + + band_jam_track_count = @jam_track.band_jam_track_count + jam_track_count = JamTrack.count + @title = individual_jamtrack_title(false, params[:generic], @jam_track) + @description = individual_jamtrack_desc(false, params[:generic], @jam_track) + @page_data = {jam_track: @jam_track, all_track_count: jam_track_count, band_track_count: band_jam_track_count, band: false, generic: params[:generic], instrument: instrument_name, instrument_id: instrument_id, instrument_count: instrument_count} + gon.jam_track_plan_code = @jam_track.plan_code if @jam_track + gon.generic = params[:generic] + gon.instrument_id = instrument_id + + render 'buy_gift_card', layout: 'web' + end end diff --git a/web/app/views/api_shopping_carts/add_gift_card.rabl b/web/app/views/api_shopping_carts/add_gift_card.rabl new file mode 100644 index 000000000..272eb3c6d --- /dev/null +++ b/web/app/views/api_shopping_carts/add_gift_card.rabl @@ -0,0 +1,5 @@ +extends "api_shopping_carts/show" + +node :show_free_jamtrack do + any_user.show_free_jamtrack? +end \ No newline at end of file diff --git a/web/app/views/api_shopping_carts/add_jamtrack.rabl b/web/app/views/api_shopping_carts/add_jamtrack.rabl index c61e7b52b..272eb3c6d 100644 --- a/web/app/views/api_shopping_carts/add_jamtrack.rabl +++ b/web/app/views/api_shopping_carts/add_jamtrack.rabl @@ -1 +1,5 @@ -extends "api_shopping_carts/show" \ No newline at end of file +extends "api_shopping_carts/show" + +node :show_free_jamtrack do + any_user.show_free_jamtrack? +end \ No newline at end of file diff --git a/web/app/views/api_shopping_carts/remove_cart.rabl b/web/app/views/api_shopping_carts/remove_cart.rabl new file mode 100644 index 000000000..35e9a2b48 --- /dev/null +++ b/web/app/views/api_shopping_carts/remove_cart.rabl @@ -0,0 +1,3 @@ +node :show_free_jamtrack do + any_user.show_free_jamtrack? +end \ No newline at end of file diff --git a/web/app/views/api_shopping_carts/update_cart.rabl b/web/app/views/api_shopping_carts/update_cart.rabl new file mode 100644 index 000000000..35e9a2b48 --- /dev/null +++ b/web/app/views/api_shopping_carts/update_cart.rabl @@ -0,0 +1,3 @@ +node :show_free_jamtrack do + any_user.show_free_jamtrack? +end \ No newline at end of file diff --git a/web/app/views/api_users/show.rabl b/web/app/views/api_users/show.rabl index 9c9c8af0a..6b9f86be5 100644 --- a/web/app/views/api_users/show.rabl +++ b/web/app/views/api_users/show.rabl @@ -11,15 +11,15 @@ end # give back more info if the user being fetched is yourself if @user == current_user - attributes :email, :original_fpfile, :cropped_fpfile, :crop_selection, :session_settings, :show_whats_next, :show_whats_next_count, :subscribe_email, :auth_twitter, :new_notifications, :sales_count, :reuse_card, :purchased_jamtracks_count, :first_downloaded_client_at, :created_at, :first_opened_jamtrack_web_player + attributes :email, :original_fpfile, :cropped_fpfile, :crop_selection, :session_settings, :show_whats_next, :show_whats_next_count, :subscribe_email, :auth_twitter, :new_notifications, :sales_count, :reuse_card, :purchased_jamtracks_count, :first_downloaded_client_at, :created_at, :first_opened_jamtrack_web_player, :gifted_jamtracks, :has_redeemable_jamtrack node :geoiplocation do |user| geoiplocation = current_user.geoiplocation geoiplocation.info if geoiplocation end - node :free_jamtrack do |user| - Rails.application.config.one_free_jamtrack_per_user && user.has_redeemable_jamtrack + node :show_free_jamtrack do |user| + user.show_free_jamtrack? end node :show_jamtrack_guide do |user| diff --git a/web/app/views/clients/_account_profile_avatar.html.erb b/web/app/views/clients/_account_profile_avatar.html.erb index c9dc2eb49..fb53c0c41 100644 --- a/web/app/views/clients/_account_profile_avatar.html.erb +++ b/web/app/views/clients/_account_profile_avatar.html.erb @@ -44,5 +44,5 @@ diff --git a/web/app/views/clients/_checkout_complete.html.slim b/web/app/views/clients/_checkout_complete.html.slim index d6c09da27..eaf440ff3 100644 --- a/web/app/views/clients/_checkout_complete.html.slim +++ b/web/app/views/clients/_checkout_complete.html.slim @@ -19,6 +19,8 @@ div layout="screen" layout-id="checkoutComplete" id="checkoutCompleteScreen" cla br .thanks-detail We'll send you an email confirming your order shortly. br + .thanks-detail.gift-card.hidden + p Thank you for purchasing a JamTrack Gift Card! It will be mailed to you. .thanks-detail.jam-tracks-in-browser.hidden p To play your purchased JamTrack, launch the JamKazam application and open the JamTrack while in a session. a.download-jamkazam-wrapper.hidden href="/downloads" rel="external" diff --git a/web/app/views/clients/_checkout_order.html.slim b/web/app/views/clients/_checkout_order.html.slim index 1a62e7066..3726e782f 100644 --- a/web/app/views/clients/_checkout_order.html.slim +++ b/web/app/views/clients/_checkout_order.html.slim @@ -90,7 +90,7 @@ script type='text/template' id='template-order-content' = "{% _.each(data.carts, function(cart) { %}" .cart-item cart-id="{{cart.id}}" .cart-item-caption - = "{{cart.cart_type}}: {{cart.product_info.name}}" + = "{{cart.product_info.sale_display}}" = "{% if (cart.product_info.free) { %}" span.first-one-free | (first one free) diff --git a/web/app/views/clients/_checkout_payment.html.slim b/web/app/views/clients/_checkout_payment.html.slim index 1bf5794a4..5f1d1d323 100644 --- a/web/app/views/clients/_checkout_payment.html.slim +++ b/web/app/views/clients/_checkout_payment.html.slim @@ -10,8 +10,7 @@ div layout="screen" layout-id="checkoutPayment" id="checkoutPaymentScreen" class .checkout-navigation-bar .payment-wrapper p.payment-prompt.free-jamtrack.hidden - | Please enter your billing address and payment information below. You will not be billed for your first JamTrack, which is 100% free.  - | But we need this data to prevent fraud/abuse of those who would create multiple accounts to collect multiple free JamTracks.  + | Please enter your billing address and payment information below.  | You will not be billed for any charges of any kind without your explicit authorization.  | There are no "hidden" charges or fees, thank you! p.payment-prompt.no-free-jamtrack.hidden diff --git a/web/app/views/clients/_checkout_signin.html.slim b/web/app/views/clients/_checkout_signin.html.slim index 620bfc00d..2f26d4ae6 100644 --- a/web/app/views/clients/_checkout_signin.html.slim +++ b/web/app/views/clients/_checkout_signin.html.slim @@ -47,10 +47,10 @@ div layout="screen" layout-id="checkoutSignin" id="checkoutSignInScreen" class=" p.facebook-prompt Or sign in using Facebook: = link_to image_tag("content/button_facebook_signin.png", {:width => 249, :height => 46}), '/auth/facebook', class: "signin-facebook" .right-side - h3 NOT A MEMBER YET? + h3 NOT A MEMBER? p.signup-later-prompt - | Thousands of musicians are now registered members of JamKazam. Click the NEXT button below to join us, and welcome! + | Click the NEXT button below to enter the address and payment information for your purchase. .actions a.btnNext.button-orange NEXT diff --git a/web/app/views/clients/_jamtrack_browse.html.slim b/web/app/views/clients/_jamtrack_browse.html.slim deleted file mode 100644 index f7d7b515a..000000000 --- a/web/app/views/clients/_jamtrack_browse.html.slim +++ /dev/null @@ -1,86 +0,0 @@ -#jamtrackScreen.screen.secondary.no-login-required layout='screen' layout-id='jamtrackBrowse' - .content - .content-head - .content-icon=image_tag("content/icon_jamtracks.png", height:19, width:19 ) - h1 jamtracks - =render "screen_navigation" - .content-body - =form_tag('', {:id => 'jamtrack-find-form', :class => 'inner-content'}) do - =render(:partial => "web_filter", :locals => {:search_type => Search::PARAM_JAMTRACK}) - .filter-body - .content-body-scroller - .profile-wrapper - h2 shop for jamtracks - table.generaltable.jamtrack-table - thead - tr - th.jamtrack-detail JAMTRACK - th.jamtrack-tracks TRACKS INCLUDED / PREVIEW - th.jamtrack-action SHOP - tbody.jamtrack-content - a.btn-next-pager href="/api/jamtracks?page=1" Next - .end-of-jamtrack-list.end-of-list="No more Jamtracks" - -script#template-jamtrack type='text/template' - tr.jamtrack-record jamtrack-id="{{data.jamtrack.id}}" - td.jamtrack-detail - .jamtrack-name - | "{{data.jamtrack.name}}" - .jamtrack-original-artist - | by {{data.jamtrack.original_artist}} - br clear="all" - .clearall.detail-label.extra.hidden.song-writer - | Songwriters: - .detail-value.extra.hidden - | {{data.jamtrack.songwriter}} - .clearall.detail-label.extra.hidden - | Publishers: - .detail-value.extra.hidden - | {{data.jamtrack.publisher}} - .clearall.detail-label.extra.hidden - | Genre: - .detail-value.extra.hidden - | {{data.jamtrack.genres[0]}} - .clearall.detail-label.extra.hidden - | Version: - .detail-value.extra.hidden - | {{data.jamtrack.recording_type}} - td.jamtrack-tracks - .detail-arrow - .jamtrack-detail-btn - ="{% if (data.expanded) { %}" - | hide tracks - a.details-arrow.arrow-up - ="{% } else { %}" - | show all tracks - a.details-arrow.arrow-down - ="{% } %}" - ="{% _.each(data.jamtrack.tracks, function(track) { %}" - .jamtrack-track.hidden jamtrack-track-id="{{track.id}}" - / .instrument-desc - / | {{track.instrument_desc}} - /.track-instrument - .jamtrack-preview - .clearall - ="{% }); %}" - td.jamtrack-action - .jamtrack-action-container - .jamtrack-actions - / a.play-button href="#" data-jamtrack-id="{{data.jamtrack.id}}" - / =image_tag "shared/play_button.png" - .jamtrack-price class="{{data.free_state}}" - | {{"$ " + data.jamtrack.price}} - ="{% if (data.is_free) { %}" - a.jamtrack-add-cart.button-orange.is_free href="#" data-jamtrack-id="{{data.jamtrack.id}}" GET IT FREE! - ="{% } else if (data.jamtrack.purchased) { %}" - a.jamtrack-add-cart-disabled.button-grey.button-disabled href="javascript:void(0)" PURCHASED - ="{% } else if (data.jamtrack.added_cart) { %}" - a.jamtrack-add-cart-disabled.button-grey.button-disabled href="client#/shoppingCart" ALREADY IN CART - ="{% } else { %}" - a.jamtrack-add-cart.button-orange href="#" data-jamtrack-id="{{data.jamtrack.id}}" ADD TO CART - ="{% }; %}" - ="{% if (data.jamtrack.sales_region==JK.AVAILABILITY_US) { %}" - .jamtrack-license - | This JamTrack available only to US customers.      - a.license-us-why.orange href="#" why? - ="{% }; %}" diff --git a/web/app/views/clients/_order.html.slim b/web/app/views/clients/_order.html.slim deleted file mode 100644 index cc8982dfe..000000000 --- a/web/app/views/clients/_order.html.slim +++ /dev/null @@ -1,290 +0,0 @@ -div layout="screen" layout-id="order" id="orderScreen" class="screen secondary" - .content - .content-head - .content-icon= image_tag("content/icon_shopping_cart.png", {:height => 19, :width => 19}) - h1 check out - = render "screen_navigation" - .content-body - #order_error.error.hidden - .content-body-scroller - .content-wrapper - .checkout-navigation-bar - .checkout-payment-info - form class="payment-info" id="checkout-payment-info" - .billing-address - h2.billing-caption Billing Address - #divBillingFirstName - .billing-label - label for="billing-first-name" First Name: - .billing-value - input type="text" id="billing-first-name" - .clearall - #divBillingLastName - .billing-label - label for="billing-last-name" Last Name: - .billing-value - input type="text" id="billing-last-name" - .clearall - #divBillingAddress1 - .billing-label - label for="billing-address1" Address 1: - .billing-value - input type="text" id="billing-address1" - .clearall - .billing-label - label for="billing-address2" Address 2: - .billing-value - input type="text" id="billing-address2" - .clearall - #divBillingCity - .billing-label - label for="billing-city" City: - .billing-value - input type="text" id="billing-city" - .clearall - #divBillingState - .billing-label - label for="billing-state" State/Region: - .billing-value - input type="text" id="billing-state" - .clearall - #divBillingZip - .billing-label - label for="billing-zip" Zip: - .billing-value - input type="text" id="billing-zip" - .clearall - #divBillingCountry - .billing-label - label for="billing-country" Country: - .billing-value - input type="text" id="billing-country" - .clearall - .payment-method - h2.payment-method-caption Payment Method - b Enter Card Information - br - #divCardName - .card-label.mt10 - label for="card-name" Name of Card: - .card-value.mt10 - input type="text" id="card-name" - .clearall - #divCardNumber - .card-label.mt10 - label for="card-number" Card Number: - .card-value.mt10 - input type="text" id="card-number" - .clearall - #divCardExpiry - .card-label.mt10 Expiration Date: - .card-value.mt10 - =date_select("card", "expire-date", use_two_digit_numbers: true, discard_day: true, :start_year => Time.now.year, :end_year => Time.now.year + 18, :order => [:month, :day, :year], :default => -25.years.from_now, :html=>{:class => "account-profile-birthdate", :id=>"card-expiry"} ) - .clearall - #divCardVerify - .card-label.mt10 - label for="card-verify" Verification Value: - .card-value.mt10 - input type="text" id="card-verify" - .clearall - .card-label.mt15 - .card-value.mt15 - .save-card-checkbox.ichecbuttons - input type="checkbox" id="save-card" name="save-card" checked="checked" - .divSaveCardHelper - label for="save-card" Save card for future use - .clearall - .clearall - .clearall - - .action-bar.mt15 - .shipping-address - h2.shipping-address-label Shipping Address - .shipping-as-billing.ichecbuttons - input type="checkbox" id="shipping-as-billing" name="shipping-as-billing" checked="checked" - .divBillingHelper - label for="shipping-as-billing" Same as billing address - .clearall - .shipping-address-detail.hidden - #divShippingFirstName - .shipping-label - label for="shipping-first-name" First Name: - .shipping-value - input type="text" id="shipping-first-name" - .clearall - #divShippingLastName - .shipping-label - label for="shipping-last-name" Last Name: - .shipping-value - input type="text" id="shipping-last-name" - .clearall - #divShippingAddress1 - .shipping-label - label for="shipping-address1" Address 1: - .shipping-value - input type="text" id="shipping-address1" - .clearall - .shipping-label - label for="shipping-address2" Address 2: - .shipping-value - input type="text" id="shipping-address2" - .clearall - #divShippingCity - .shipping-label - label for="shipping-city" City: - .shipping-value - input type="text" id="shipping-city" - .clearall - #divShippingState - .shipping-label - label for="shipping-state" State/Region: - .shipping-value - input type="text" id="shipping-state" - .clearall - #divShippingZip - .shipping-label - label for="shipping-zip" Zip: - .shipping-value - input type="text" id="shipping-zip" - .clearall - #divShippingCountry - .shipping-label - label for="shipping-country" Country: - .shipping-value - input type="text" id="shipping-country" - .clearall - .right.mt30 - a href="#" id="payment-info-help" class="button-grey" HELP - a href="#" id="payment-info-next" class="button-orange" NEXT - .clearall - .order-panel.hidden - .order-header.left - h2 Review Your Order - .mt5 - span By placing your order, you agree to JamKazam's - ' - a href="/corp/terms" terms of service - ' - span and - ' - a href="/corp/returns" returns policy - span . - .right.mt10 - a href="#" class="button-grey" HELP - .clearall - - .order-content - .thanks-panel.hidden - h2 Thank you for your order! - br - .thanks-detail We'll send you an email confirming your order shortly. - br - .thanks-detail.jam-tracks-in-browser.hidden - | To play your purchased JamTrack, launch the JamKazam application and open the JamTrack while in a session. - .thanks-detail.purchased-jam-track.hidden - h2.purchased-jam-track-header Downloading Your Purchased JamTracks - span Each JamTrack will be downloaded sequentially. - br - span.notice Note that you do not have to wait for this to complete in order to use your JamTrack later. - br.clear - ul.purchased-list - - - -script type='text/template' id='template-order-content' - .order-left-page - .payment-info-page - .address-info - b.left Billing Address - a.left.ml5.change-payment-info href="#" change - .clearall - span.mt5= "{{data.billing_info.first_name}} {{data.billing_info.last_name}}" - br - span.mt5= "{{data.billing_info.address1}}" - br - span.mt5= "{{data.billing_info.address2}}" - br - br - b.left Shipping Address - a.left.ml5.change-payment-info href="#" change - .clearall - = "{% if (data.shipping_as_billing) { %}" - span.mt5 same as billing address - = "{% } else { %}" - span.mt5= "{{data.shipping_info.first_name}} {{data.shipping_info.last_name}}" - br - span.mt5= "{{data.shipping_info.address1}}" - br - span.mt5= "{{data.shipping_info.address2}}" - = "{% } %}" - br - .payment-method-info - b.left Payment Method - a.left.ml5.change-payment-info href="#" change - .clearall - - /= image_tag '' - ="Ending in: {{data.billing_info.number.slice(-4)}}" - - .clearall - .order-items-page - .cart-items - .cart-item-caption#header - span Your order includes: - .cart-item-price - span style="text-decoration: underline;" Price - .cart-item-quantity - span style="text-decoration: underline;" Quantity - .clearall - = "{% if (data.carts.length == 0) { %}" - .no-cart-items You have no orders now. - = "{% } %}" - = "{% _.each(data.carts, function(cart) { %}" - .cart-item cart-id="{{cart.id}}" - .cart-item-caption - = "{{cart.cart_type}}: {{cart.product_info.name}}" - .cart-item-price - = "$ {{cart.product_info.price}}" - .cart-item-quantity - = "{{cart.quantity}}" - .clearall - = "{% }); %}" - .clearall - .order-right-page - a href="#" class="button-orange place-order" PLACE YOUR ORDER - br - br - b.mt10 Order Summary: - br - .left Items: - .right= "${{data.sub_total}}" - .clearall - .left Shipping & handling - .right $0.00 - br - hr - .left Total before Tax: - .right= "${{data.sub_total}}" - .clearall - .left Taxes: - .right= "${{data.taxes}}" - .clearall - br - hr - b.order-total - .left Order Total: - .right= "${{data.grand_total}}" - .clearall - br - div style="text-align: left;" - span By placing your order, you agree to JamKazam's - ' - a href="/corp/terms" terms of service - ' - span and - ' - a href="/corp/returns" returns policy - span . - -script type='text/template' id='template-purchased-jam-track' - li data-jam-track-id="{{data.jam_track_id}}" \ No newline at end of file diff --git a/web/app/views/clients/_redeem_complete.html.slim b/web/app/views/clients/_redeem_complete.html.slim index f57cbe31a..3c2ba8ab5 100644 --- a/web/app/views/clients/_redeem_complete.html.slim +++ b/web/app/views/clients/_redeem_complete.html.slim @@ -27,6 +27,9 @@ div layout="screen" layout-id="redeemComplete" id="redeemCompleteScreen" class=" .download-jamkazam | Click Here to Get the Free JamKazam Application + a.back-to-browsing href="/client#/jamtrack" + | or click here to browse more jamtracks + .jam-tracks-in-client.hidden h2 Congratulations on getting your JamTrack! diff --git a/web/app/views/clients/_shopping_cart.html.haml b/web/app/views/clients/_shopping_cart.html.haml index 1db6fe56b..a4107c934 100644 --- a/web/app/views/clients/_shopping_cart.html.haml +++ b/web/app/views/clients/_shopping_cart.html.haml @@ -14,7 +14,7 @@ %div 1 item added to shopping cart %div - {{data.cart_type}}: {{data.product_info.name}} + {{data.product_info.sale_display}} = "{% if (data.any_in_us) { %}" .note Note: You must be in the United States to purchase this {{data.type}} due to licensing constraints. @@ -40,7 +40,7 @@ = "{% _.each(data.carts, function(cart, index) { %}" %tr.cart-item{"cart-id" => "{{cart.id}}"} %td.cart-item-caption - {{cart.cart_type}}: {{cart.product_info.name}} + {{cart.product_info.sale_display}} %td.cart-item-price $ {{Number(cart.product_info.real_price).toFixed(2)}} = "{% if(index == data.carts.length - 1) { %}" diff --git a/web/app/views/clients/index.html.erb b/web/app/views/clients/index.html.erb index 962e21298..0dd83c6ae 100644 --- a/web/app/views/clients/index.html.erb +++ b/web/app/views/clients/index.html.erb @@ -48,7 +48,6 @@ <%= render "checkout_complete" %> <%= render "redeem_signup" %> <%= render "redeem_complete" %> -<%= render "order" %> <%= render "feed" %> <%= render "bands" %> <%= render "musicians" %> @@ -122,14 +121,14 @@ JK.currentUserName = '<%= current_user.name %>'; JK.currentUserMusician = '<%= current_user.musician %>'; JK.currentUserAdmin = <%= current_user.admin %>; - JK.currentUserFreeJamTrack = <%= APP_CONFIG.one_free_jamtrack_per_user && current_user.has_redeemable_jamtrack %> + JK.currentUserFreeJamTrack = <%= current_user.show_free_jamtrack? %> <% else %> JK.currentUserId = null; JK.currentUserAvatarUrl = null; JK.currentUserName = null; JK.currentUserMusician = null; JK.currentUserAdmin = false; - JK.currentUserFreeJamTrack = <%= APP_CONFIG.one_free_jamtrack_per_user && anonymous_user.nil? ? false : anonymous_user.has_redeemable_jamtrack%> + JK.currentUserFreeJamTrack = <%= anonymous_user.nil? ? false : anonymous_user.show_free_jamtrack? %> <% end %> diff --git a/web/app/views/landings/buy_gift_card.html.slim b/web/app/views/landings/buy_gift_card.html.slim new file mode 100644 index 000000000..cc96f704d --- /dev/null +++ b/web/app/views/landings/buy_gift_card.html.slim @@ -0,0 +1,24 @@ +- provide(:page_name, 'landing_page full individual_jamtrack') +- provide(:description, @description) +- provide(:title, @title) + += react_component 'GiftCardLandingPage', @page_data.to_json + +- content_for :after_black_bar do + .row.cta-row + h2 GET YOUR GIFT CARD NOW! + p And join 20,000+ other musicians who love our JamTracks. + p.cta-text Not sure if JamTracks are for you? Scroll down to learn more. + +- content_for :white_bar do + = react_component 'JamTrackLandingBottomPage', @page_data.to_json + +- content_for :red_bar do + .full-row + | Get your free JamTrack and start playing today! + +javascript: + $(document).on('JAMKAZAM_READY', function(e, data) { + var song = new JK.IndividualJamTrack(data.app, true); + song.initialize(); + }) diff --git a/web/app/views/landings/product_jamblaster.html.slim b/web/app/views/landings/product_jamblaster.html.slim index 8fd33fd5d..42f07d6a4 100644 --- a/web/app/views/landings/product_jamblaster.html.slim +++ b/web/app/views/landings/product_jamblaster.html.slim @@ -7,27 +7,23 @@ h1.product-headline | The JamBlaster by JamKazam p.product-description - | The JamBlaster is a device designed from the ground up to meet the unique requirements of real-time, online, distributed music performance. This device vastly extends the range/distance over which musicians can play together across the Internet. + | With your smartphone and a JamBlaster, you can: ul - li Radically reduces audio processing latency compared to today's industry standard computers and audio interfaces. - li Delivers plug-and-play ease of use, with no worries about hardware and software incompatibilities, driver problems, and arcane configurations. - li Combines both a computer and an audio interface into a single elegant device. - li Works with computers (even old crappy ones), tablets or smartphones. - li Works with your favorite recording software applications like Garage Band, Reaper, Pro Tools, etc. + li Play live in sync with other musicians from different locations over the Internet – great for rehearsals without travel or space, co-writing, or joining open jams for fun + li Make pro quality audio (and optionally video) recordings of yourself and others – both master mix and fully isolated stems + li Learn and play along with 4,000+ of your favorite songs – with the ability to solo or mute any part, slow down playback for practice, change pitch/key, and more + li Teach or take online music lessons that really work – unlike Skype and Google Hangouts, which suffer from very high latency and poor audio quality + li Broadcast live video performances with pro quality audio through YouTube to family, friends, and fans – either yourself or your band playing in one location, or your online distributed JamKazam sessions .column h1 See What You Can Do With The JamBlaster .video-wrapper .video-container - iframe src="//www.youtube.com/embed/YHZQHfKDdMc" frameborder="0" allowfullscreen="allowfullscreen" + iframe src="//www.youtube.com/embed/iteR0ciRhtw" frameborder="0" allowfullscreen="allowfullscreen" br clear="all" .row .column - h1 - | Want a JamBlaster? Need One? - p If you are a registered member of the JamKazam community, and if you "know" you will buy a JamBlaster for $199 as soon as they become available, then click the button below to add yourself to our wait list. When we get enough "virtual orders", we'll reach back out to all signups to take real orders. - .cta-big-button - a.white-bordered-button href="#" SIGN UP TO BUY A JAMBLASTER + a.white-bordered-button href="https://www.kickstarter.com/projects/1091884999/jamblaster-the-ultimate-smartphone-accessory-for-m" Get JamBlaster On Kickstarter .column h1 Want To Know More About Latency? p @@ -74,51 +70,6 @@ javascript: promptConfirmed(data.app) } - $('.product_jamblaster a.white-bordered-button').on('click', function () { - var rest = window.JK.Rest(); - if (window.JK.currentUserId) { - window.JK.Banner.showYesNo({ - title: "please confirm", - html: "Are you sure you want to make a virtual order for a JamBlaster?", - yes: function () { - confirmOrder(data.app) - }, - no: function () { - window.JK.Banner.hide(); - } - }) - } - else { - window.JK.Banner.showAlert({ - buttons: [ - { - name: 'CANCEL', click: function () { + return false; + }) - } - }, - { - name: 'SIGN IN', buttonStyle: 'button-orange', click: function () { - var redirectPath = '?redirect-to=' + encodeURIComponent('/products/jamblaster?want_jamblaster=true'); - window.location.href = '/signin' + redirectPath; - } - }, - { - name: 'SIGN UP', click: function () { - rest.createSignupHint({redirect_location: '/products/jamblaster', want_jamblaster:true}) - .done(function() { - window.location.href = '/signup' - }) - .fail(function() { - data.app.layout.notify("Unable to take your virtual order at this time. Please try again later.") - }) - } - } - ], - title: 'please sign in or sign up', - html: "

    In order to place a virtual order for a JamBlaster, you must be a registered JamKazam user. Please sign in or sign up to place your order.

    " - }) - } - - return false; - }) - }) \ No newline at end of file diff --git a/web/app/views/landings/redeem_giftcard.html.slim b/web/app/views/landings/redeem_giftcard.html.slim new file mode 100644 index 000000000..589fc7fd4 --- /dev/null +++ b/web/app/views/landings/redeem_giftcard.html.slim @@ -0,0 +1,5 @@ +- provide(:page_name, 'landing_page full redeem_giftcard') +- provide(:description, 'Here you can redeem a gift card and associate it with your JamKazam account.') +- provide(:title, 'Redeem a Gift Card') + += react_component 'RedeemGiftCardPage' diff --git a/web/app/views/layouts/web.html.erb b/web/app/views/layouts/web.html.erb index 4c09df132..85c1ea1eb 100644 --- a/web/app/views/layouts/web.html.erb +++ b/web/app/views/layouts/web.html.erb @@ -113,13 +113,13 @@ JK.currentUserAvatarUrl = JK.resolveAvatarUrl('<%= current_user.photo_url %>'); JK.currentUserName = '<%= current_user.name %>'; JK.currentUserMusician = '<%= current_user.musician %>'; - JK.currentUserFreeJamTrack = <%= APP_CONFIG.one_free_jamtrack_per_user && current_user.has_redeemable_jamtrack %> - <% else %> - JK.currentUserId = null; + JK.currentUserFreeJamTrack = <%= current_user.show_free_jamtrack? %> + <% else %> + JK.currentUserId = null; JK.currentUserAvatarUrl = null; JK.currentUserName = null; JK.currentUserMusician = null; - JK.currentUserFreeJamTrack = <%= APP_CONFIG.one_free_jamtrack_per_user && anonymous_user.nil? ? false : anonymous_user.has_redeemable_jamtrack%> + JK.currentUserFreeJamTrack = <%= anonymous_user.nil? ? false : anonymous_user.show_free_jamtrack? %> <% end %> diff --git a/web/app/views/users/home.html.slim b/web/app/views/users/home.html.slim index a7fd408cf..112998f47 100644 --- a/web/app/views/users/home.html.slim +++ b/web/app/views/users/home.html.slim @@ -33,7 +33,7 @@ br clear="all" .home-column.last - = link_to image_tag("web/thumbnail_jamblaster.jpg", :alt => "JamBlaster explanatory video!"), '#', class: "jamblaster-video video-item", 'data-video-header' => 'JamBlaster', 'data-video-url' => 'https://www.youtube.com/embed/YHZQHfKDdMc?autoplay=1' + = link_to image_tag("web/thumbnail_jamblaster.jpg", :alt => "JamBlaster explanatory video!"), '#', class: "jamblaster-video video-item", 'data-video-header' => 'JamBlaster', 'data-video-url' => 'https://www.youtube.com/embed/iteR0ciRhtw?autoplay=1' h3 Ultra Low-Latency Audio Interface p @@ -41,7 +41,7 @@ strong JamBlaster |  is a device designed from the ground up to meet the requirements of online music play, vastly extending the range over which musicians can play together across the Internet. - = link_to image_tag("web/button_cta_jamblaster.png", width: 234, height: 57), '/products/jamblaster', class: 'cta-button jamblaster' + = link_to image_tag("web/button_cta_jamblaster.png", width: 234, height: 57), 'https://www.kickstarter.com/projects/1091884999/jamblaster-the-ultimate-smartphone-accessory-for-m', class: 'cta-button jamblaster' .extra-links .learn-more a.learn-more-jamblaster href='/products/jamblaster' learn more diff --git a/web/config/initializers/dev_users.rb b/web/config/initializers/dev_users.rb index 528a5f9cf..2314a562b 100644 --- a/web/config/initializers/dev_users.rb +++ b/web/config/initializers/dev_users.rb @@ -11,7 +11,6 @@ if Rails.env == "development" && Rails.application.config.bootstrap_dev_users User.create_dev_user("David", "Wilson", "david@jamkazam.com", "jam123", "Austin", "TX", "US", nil, 'http://www.jamkazam.com/assets/avatars/avatar_david.jpg') User.create_dev_user("Jonathan", "Kolyer", "jonathan@jamkazam.com", "jam123", "Austin", "TX", "US", nil, nil) User.create_dev_user("Oswald", "Becca", "os@jamkazam.com", "jam123", "Austin", "TX", "US", nil, nil) - User.create_dev_user("Anthony", "Davis", "anthony@jamkazam.com", "jam123", "Austin", "TX", "US", nil, nil) User.create_dev_user("Steven", "Miers", "steven@jamkazam.com", "jam123", "Austin", "TX", "US", nil, nil) end diff --git a/web/config/routes.rb b/web/config/routes.rb index 658db1c33..cb96586fe 100644 --- a/web/config/routes.rb +++ b/web/config/routes.rb @@ -21,6 +21,8 @@ SampleApp::Application.routes.draw do match '/signin', to: 'sessions#create', via: :post match '/signout', to: 'sessions#destroy', via: :delete + match '/redeem_giftcard', to: 'landings#redeem_giftcard', via: :get + # landing pages match '/landing/wb', to: 'landings#watch_bands', via: :get, as: 'landing_wb' match '/landing/wo', to: 'landings#watch_overview', via: :get, as: 'landing_wo' @@ -39,6 +41,7 @@ SampleApp::Application.routes.draw do match '/landing/jamtracks/:plan_code', to: 'landings#individual_jamtrack', via: :get, as: 'individual_jamtrack' match '/landing/jamtracks/band/:plan_code', to: 'landings#individual_jamtrack_band', via: :get, as: 'individual_jamtrack_band' match '/landing/jamtracks/:instrument/:plan_code', to: 'landings#individual_jamtrack', via: :get, as: 'individual_jamtrack_instrument' + match '/landing/gift-card', to: 'landings#buy_gift_card', via: :get, as: 'buy_gift_card' match '/affiliateProgram', to: 'landings#affiliate_program', via: :get, as: 'affiliate_program' @@ -274,6 +277,7 @@ SampleApp::Application.routes.draw do match '/shopping_carts' => 'api_shopping_carts#index', :via => :get 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/add_gift_card' => 'api_shopping_carts#add_gift_card', :via => :post # RSVP requests match '/rsvp_requests' => 'api_rsvp_requests#index', :via => :get @@ -325,6 +329,7 @@ SampleApp::Application.routes.draw do match '/recurly/billing_info' => 'api_recurly#billing_info', :via => :get match '/recurly/update_billing_info' => 'api_recurly#update_billing_info', :via => :put match '/recurly/place_order' => 'api_recurly#place_order', :via => :post + match '/ios/order_placed' => 'api_jam_tracks#ios_order_placed', :via => :post # sale info match '/payment_histories' => 'api_payment_histories#index', :via => :get @@ -452,6 +457,9 @@ SampleApp::Application.routes.draw do match '/users/:id/syncs/:user_sync_id' => 'api_user_syncs#show', :via => :get match '/users/:id/syncs/deletables' => 'api_user_syncs#deletables', :via => :post + # giftcards + match '/users/:id/gift_cards' => 'api_users#redeem_giftcard', :via => :post + # bands diff --git a/web/lib/tasks/jam_tracks.rake b/web/lib/tasks/jam_tracks.rake index bbabe08e5..c0add4f92 100644 --- a/web/lib/tasks/jam_tracks.rake +++ b/web/lib/tasks/jam_tracks.rake @@ -1,5 +1,15 @@ namespace :jam_tracks do + task import_click_tracks: :environment do |task, args| + JamTrackImporter.storage_format = 'Tency' + JamTrackImporter.import_click_tracks + end + + task generate_jmep: :environment do |task, args| + JamTrackImporter.storage_format = 'Tency' + JamTrackImporter.generate_jmeps + end + task dry_run: :environment do |task, args| JamTrackImporter.dry_run end @@ -9,6 +19,11 @@ namespace :jam_tracks do JamTrackImporter.dry_run end + task timtracks_dry_run: :environment do |task, args| + JamTrackImporter.storage_format = 'TimTracks' + JamTrackImporter.dry_run + end + task tency_create_masters: :environment do |task, args| JamTrackImporter.storage_format = 'Tency' JamTrackImporter.create_masters @@ -75,6 +90,11 @@ namespace :jam_tracks do JamTrackImporter.synchronize_all(skip_audio_upload:false) end + task sync_tim_tracks: :environment do |task, args| + JamTrackImporter.storage_format = 'TimTracks' + JamTrackImporter.synchronize_all(skip_audio_upload:false) + end + task tency_dups: :environment do |task, args| end @@ -176,6 +196,12 @@ namespace :jam_tracks do mapper.correlate end + task touch: :environment do |task, arg| + JamTrack.all.each do |jt| + jt.touch # causes jmep re-spin + end + end + task generate_private_key: :environment do |task, arg| JamTrackRight.all.each do |right| if right.private_key_44.nil? || right.private_key_48.nil? diff --git a/web/lib/user_manager.rb b/web/lib/user_manager.rb index 8f231a57b..5fd12f487 100644 --- a/web/lib/user_manager.rb +++ b/web/lib/user_manager.rb @@ -29,6 +29,7 @@ class UserManager < BaseManager any_user = options[:any_user] signup_hint = options[:signup_hint] affiliate_partner = options[:affiliate_partner] + gift_card = options[:gift_card] recaptcha_failed = false unless options[:skip_recaptcha] # allow callers to opt-of recaptcha @@ -72,7 +73,8 @@ class UserManager < BaseManager affiliate_referral_id: affiliate_referral_id, any_user: any_user, signup_hint: signup_hint, - affiliate_partner: affiliate_partner) + affiliate_partner: affiliate_partner, + gift_card: gift_card) user end diff --git a/web/spec/controllers/api_jam_tracks_controller_spec.rb b/web/spec/controllers/api_jam_tracks_controller_spec.rb index 46a7d9c2d..fe4853d14 100644 --- a/web/spec/controllers/api_jam_tracks_controller_spec.rb +++ b/web/spec/controllers/api_jam_tracks_controller_spec.rb @@ -26,6 +26,16 @@ describe ApiJamTracksController do controller.current_user = @user end + describe "ios_order_placed" do + it "succeeds" do + post :ios_order_placed, { jam_track_id: @jam_track.id } + + response.status.should == 200 + right = @jam_track.right_for_user(@user) + right.id.should eq(JSON.parse(response.body)["jam_track_right_id"]) + end + end + describe "admin" do before(:each) do @admin = FactoryGirl.create(:admin) diff --git a/web/spec/controllers/api_shopping_carts_controller_spec.rb b/web/spec/controllers/api_shopping_carts_controller_spec.rb index c6f196d0d..b5efb7fa8 100644 --- a/web/spec/controllers/api_shopping_carts_controller_spec.rb +++ b/web/spec/controllers/api_shopping_carts_controller_spec.rb @@ -25,11 +25,14 @@ describe ApiShoppingCartsController do it "add_jamtrack" do post :add_jamtrack, {:format => 'json', id: jam_track.id} - response.status.should == 201 + response.status.should == 200 end it "index" do cart = ShoppingCart.create(user, jam_track) + cart.errors.any?.should be_false + user.reload + user.shopping_carts.count.should eq(1) get :index, {:format => 'json'} response.status.should == 200 @@ -41,7 +44,7 @@ describe ApiShoppingCartsController do it "remove_cart" do cart = ShoppingCart.create(user, jam_track) delete :remove_cart, {:format => 'json', id: cart.id} - response.status.should == 204 + response.status.should == 200 ShoppingCart.find_by_id(cart.id).should be_nil end @@ -64,7 +67,9 @@ describe ApiShoppingCartsController do it "add_jamtrack" do post :add_jamtrack, {:format => 'json', id: jam_track.id} - response.status.should == 201 + response.status.should == 200 + user.reload + user.shopping_carts.count.should eql(1) end it "index" do @@ -80,7 +85,7 @@ describe ApiShoppingCartsController do it "remove_cart" do cart = ShoppingCart.create(user, jam_track) delete :remove_cart, {:format => 'json', id: cart.id} - response.status.should == 204 + response.status.should == 200 ShoppingCart.find_by_id(cart.id).should be_nil end diff --git a/web/spec/controllers/api_users_controller_spec.rb b/web/spec/controllers/api_users_controller_spec.rb index 065602e77..b57746459 100644 --- a/web/spec/controllers/api_users_controller_spec.rb +++ b/web/spec/controllers/api_users_controller_spec.rb @@ -5,12 +5,134 @@ describe ApiUsersController do let (:user) { FactoryGirl.create(:user) } let (:conn) { FactoryGirl.create(:connection, user: user, last_jam_audio_latency: 5) } + let (:jam_track) { FactoryGirl.create(:jam_track)} before(:each) do controller.current_user = user end + describe "redeem_giftcard" do + let!(:gift_card) {FactoryGirl.create(:gift_card)} + + it "can succeed" do + post :redeem_giftcard, id:user.id, gift_card: gift_card.code, format:'json' + + response.should be_success + + user.reload + gift_card.reload + + user.gift_cards.should eq([gift_card]) + user.gifted_jamtracks.should eq(5) + gift_card.user.should eq(user) + end + + it "indicates if you've redeemed it" do + gift_card.user = user + gift_card.save! + + post :redeem_giftcard, id:user.id, gift_card: gift_card.code, format:'json' + + response.status.should eq(422) + error_data = JSON.parse(response.body) + error_data['errors']['gift_card'].should eq(["already redeemed by you"]) + + user.reload + gift_card.reload + + user.gift_cards.should eq([gift_card]) + user.gifted_jamtracks.should eq(5) + gift_card.user.should eq(user) + end + + it "indicates if someone else has redeemed it" do + user2 = FactoryGirl.create(:user) + gift_card.user = user2 + gift_card.save! + + post :redeem_giftcard, id:user.id, gift_card: gift_card.code, format:'json' + + response.status.should eq(422) + error_data = JSON.parse(response.body) + error_data['errors']['gift_card'].should eq(["already redeemed by another"]) + + user.reload + gift_card.reload + + user.gift_cards.should eq([]) + user.gifted_jamtracks.should eq(0) + gift_card.user.should eq(user2) + end + + it "marks free shopping cart item as free" do + # sort of a 'do nothing' really + cart1 = ShoppingCart.add_jam_track_to_cart(user, jam_track) + cart1.marked_for_redeem.should eq(1) + + post :redeem_giftcard, id:user.id, gift_card: gift_card.code, format:'json' + + response.should be_success + + user.reload + gift_card.reload + + user.gift_cards.should eq([gift_card]) + user.gifted_jamtracks.should eq(5) + gift_card.user.should eq(user) + cart1.reload + cart1.marked_for_redeem.should eq(1) + end + + it "marks non-free shopping cart item as free" do + # sort of a 'do nothing' really + user.has_redeemable_jamtrack = false + user.save! + + cart1 = ShoppingCart.add_jam_track_to_cart(user, jam_track) + cart1.marked_for_redeem.should eq(0) + + post :redeem_giftcard, id:user.id, gift_card: gift_card.code, format:'json' + + response.should be_success + + user.reload + gift_card.reload + + user.gift_cards.should eq([gift_card]) + user.gifted_jamtracks.should eq(5) + gift_card.user.should eq(user) + cart1.reload + cart1.marked_for_redeem.should eq(1) + end + + it "leaves shopping cart alone if too many items in it for size of new gift card" do + # sort of a 'do nothing' really + user.has_redeemable_jamtrack = false + user.save! + + 11.times do |i| + jamtrack = FactoryGirl.create(:jam_track) + cart1 = ShoppingCart.add_jam_track_to_cart(user, jamtrack) + cart1.marked_for_redeem.should eq(0) + end + + post :redeem_giftcard, id:user.id, gift_card: gift_card.code, format:'json' + + response.should be_success + + user.reload + gift_card.reload + + user.gift_cards.should eq([gift_card]) + user.gifted_jamtracks.should eq(5) + gift_card.user.should eq(user) + user.shopping_carts.each do |cart| + cart.marked_for_redeem.should eq(0) + end + end + end + describe "create" do it "successful" do email = 'user_create1@jamkazam.com' diff --git a/web/spec/factories.rb b/web/spec/factories.rb index 10b2d74f2..99fbb3fbe 100644 --- a/web/spec/factories.rb +++ b/web/spec/factories.rb @@ -21,6 +21,8 @@ FactoryGirl.define do subscribe_email true last_jam_audio_latency 5 reuse_card true + has_redeemable_jamtrack true + gifted_jamtracks 0 factory :fan do musician false @@ -837,4 +839,9 @@ FactoryGirl.define do factory :affiliate_legalese, class: 'JamRuby::AffiliateLegalese' do legalese Faker::Lorem.paragraphs(6).join("\n\n") end + + factory :gift_card, class: 'JamRuby::GiftCard' do + sequence(:code) {|n| n.to_s} + card_type GiftCard::JAM_TRACKS_5 + end end diff --git a/web/spec/features/checkout_spec.rb b/web/spec/features/checkout_spec.rb index c7e3e47d3..24a1c23a9 100644 --- a/web/spec/features/checkout_spec.rb +++ b/web/spec/features/checkout_spec.rb @@ -5,6 +5,7 @@ describe "Checkout", :js => true, :type => :feature, :capybara_feature => true d let(:user) { FactoryGirl.create(:user) } let(:jamtrack_acdc_backinblack) { @jamtrack_acdc_backinblack } let(:jamtrack_pearljam_evenflow) { @jamtrack_pearljam_evenflow } + let(:jamtrack_led_zeppelin_kashmir) {@jamtrack_led_zeppelin_kashmir} let(:billing_info) { { @@ -35,8 +36,11 @@ describe "Checkout", :js => true, :type => :feature, :capybara_feature => true d @recurlyClient = RecurlyClient.new @created_accounts = [] + JamTrack.delete_all + @jamtrack_acdc_backinblack = FactoryGirl.create(:jam_track, name: 'Back in Black', original_artist: 'AC/DC', sales_region: 'United States', make_track: true, plan_code: 'jamtrack-acdc-backinblack') @jamtrack_pearljam_evenflow = FactoryGirl.create(:jam_track, name: 'Even Flow', original_artist: 'Pearl Jam', sales_region: 'United States', make_track: true, plan_code: 'jamtrack-pearljam-evenflow') + @jamtrack_led_zeppelin_kashmir = FactoryGirl.create(:jam_track, name: 'Kashmir', original_artist: 'Led Zeppelin', sales_region: 'United States', make_track: true, plan_code: 'jamtrack-led-zeppelin-kashmir') # make sure plans are there @recurlyClient.create_jam_track_plan(@jamtrack_acdc_backinblack) unless @recurlyClient.find_jam_track_plan(@jamtrack_acdc_backinblack) @@ -823,8 +827,6 @@ describe "Checkout", :js => true, :type => :feature, :capybara_feature => true d acdc_sale.free.should eq(0) acdc_sale.unit_price.should eq(1.99) acdc_sale.sale.should eq(sale) - - end it "for anonymous user with referral" do @@ -1225,4 +1227,98 @@ describe "Checkout", :js => true, :type => :feature, :capybara_feature => true d acdc_sale.sale.should eq(sale) end end + + describe "gift cards" do + + it "user has both redeemable jamtrack and gift card jamtracks" do + + jamtrack_led_zeppelin_kashmir.touch + jamtrack_pearljam_evenflow.touch + + user.gifted_jamtracks = 1 + user.save! + + fast_signin(user, "/client?song=#{jamtrack_acdc_backinblack.name}#/jamtrack/search") + find('h1', text: 'jamtracks') + find("a.jamtrack-add-cart[data-jamtrack-id=\"#{jamtrack_acdc_backinblack.id}\"]", text: 'GET IT FREE!').trigger(:click) + find('.jam-tracks-in-browser') + user.reload + user.has_redeemable_jamtrack.should be_false + + find('a.back-to-browsing').trigger(:click) + find('h1', text: 'search jamtracks') + find('.search-controls .Select-control').trigger(:mousedown) + # wait for the 'Type to search' prompt to show + find('.search-controls .Select-search-prompt') + find('.search-by-string-btn').trigger(:click) + find('.jamtrack-record[data-jamtrack-id="' + jamtrack_led_zeppelin_kashmir.id + '"]') + find("a.jamtrack-add-cart[data-jamtrack-id=\"#{jamtrack_led_zeppelin_kashmir.id}\"]", text: 'GET IT FREE!').trigger(:click) + find('.jam-tracks-in-browser') + sleep 2 + user.reload + user.has_redeemable_jamtrack.should be_false + user.gifted_jamtracks.should eq(0) + + find('a.back-to-browsing').trigger(:click) + find('h1', text: 'search jamtracks') + find('.search-controls .Select-control').trigger(:mousedown) + # wait for the 'Type to search' prompt to show + find('.search-controls .Select-search-prompt') + find('.search-by-string-btn').trigger(:click) + find('.jamtrack-record[data-jamtrack-id="' + jamtrack_pearljam_evenflow.id + '"]') + find("a.jamtrack-add-cart[data-jamtrack-id=\"#{jamtrack_pearljam_evenflow.id}\"]", text: 'ADD TO CART').trigger(:click) + + find('h1', text: 'shopping cart') + find('.cart-item-caption', text: "JamTrack: #{jamtrack_pearljam_evenflow.name}") + find('.cart-item-price', text: "$ #{jamtrack_pearljam_evenflow.price}") + find('.shopping-sub-total', text:"Subtotal:$ #{jamtrack_pearljam_evenflow.price}") + + # attempt to checkout + find('a.button-orange', text: 'PROCEED TO CHECKOUT').trigger(:click) + + # should be at payment page + + # this should take us to the payment screen + find('p.payment-prompt') + + # fill out all billing info and account info + fill_in 'billing-first-name', with: 'Seth' + fill_in 'billing-last-name', with: 'Call' + fill_in 'billing-address1', with: '10704 Buckthorn Drive' + fill_in 'billing-city', with: 'Austin' + fill_in 'billing-state', with: 'Texas' + fill_in 'billing-zip', with: '78759' + fill_in 'card-number', with: '4111111111111111' + fill_in 'card-verify', with: '012' + #jk_select('US', '#checkoutPaymentScreen #divBillingCountry #billing-country') + + find('#payment-info-next').trigger(:click) + + # should be taken straight to order page + + # now see order page, and everything should no longer appear free + find('p.order-prompt') + find('.order-items-value.order-total', text:'$1.99') + find('.order-items-value.shipping-handling', text:'$0.00') + find('.order-items-value.sub-total', text:'$1.99') + find('.order-items-value.taxes', text:'$0.16') + find('.order-items-value.grand-total', text:'$2.15') + + # click the ORDER button + find('.place-order-center a.button-orange.place-order').trigger(:click) + + # and now we should see confirmation, and a notice that we are in a normal browser + find('.thanks-detail.jam-tracks-in-browser') + + user.reload + + sleep 3 # challenge to all comers! WHY DO I HAVE TO SLEEP FOR THIS ASSERTION TO BE TRUE! GAH . and 1 second won't do it + + user.reload + user.has_redeemable_jamtrack.should be_false + user.gifted_jamtracks.should eq(0) + user.purchased_jamtracks_count.should eq(3) + end + + end end diff --git a/web/spec/features/gift_card_landing_spec.rb b/web/spec/features/gift_card_landing_spec.rb new file mode 100644 index 000000000..3f67c0d88 --- /dev/null +++ b/web/spec/features/gift_card_landing_spec.rb @@ -0,0 +1,267 @@ +require 'spec_helper' + +describe "Gift Card Landing", :js => true, :type => :feature, :capybara_feature => true do + + subject { page } + + before(:all) do + ShoppingCart.delete_all + JamTrackRight.delete_all + JamTrack.delete_all + JamTrackTrack.delete_all + JamTrackLicensor.delete_all + GiftCardPurchase.delete_all + GiftCard.delete_all + end + + before(:all) do + @jamtrack_rocketman = FactoryGirl.create(:jam_track, slug: 'elton-john-rocket-man', name: 'Rocket Man', original_artist: 'Elton John', sales_region: 'United States', make_track: true, plan_code: 'jamtrack-acdc-backinblack') + end + + + let(:jamtrack_rocketman) {@jamtrack_rocketman} + let(:user) { FactoryGirl.create(:user, country: 'US') } + + let(:billing_info) { + { + first_name: 'Seth', + last_name: 'Call', + address1: '10704 Buckthorn Drive', + city: 'Austin', + state: 'Texas', + country: 'US', + zip: '78759', + number: '4111111111111111', + month: '08', + year: '2017', + verification_value: '012' + } + } + + it "logged out (5) and affiliate" do + partner = FactoryGirl.create(:affiliate_partner) + affiliate_params = partner.affiliate_query_params + visit "/landing/gift-card?" + affiliate_params + + find('h1.jam-track-name', '$10 or $20 JAMTRACKS GIFT CARDS') + find('h2.original-artist', 'A PERFECT GIFT FOR THE HOLIDAYS') + jamtrack_rocketman.jam_track_tracks.each do |track| + if track.master? + find('.tracks.previews[data-id="' + track.id + '"] img.instrument-icon[data-instrument-id="other"]') + find('.tracks.previews[data-id="' + track.id + '"] .instrument-name', text: 'Master Mix') + else + find('.tracks.previews[data-id="' + track.id + '"] img.instrument-icon[data-instrument-id="' + track.instrument.id + '"]') + find('.tracks.previews[data-id="' + track.id + '"] .instrument-name', text: track.instrument.description) + end + end + find('a.browse-all')['href'].should eq("/client?search=#/jamtrack/search") + find('button.five-jt', text: 'ADD $10 CARD TO CART').trigger(:click) + + # land in shopping cart first + find('.proceed-checkout').trigger(:click) + + # checkoutSignin page now + find('h3', text: 'NOT A MEMBER?') + + # hit 'NEXT' to create account and enter payment info + find('.btnNext.button-orange').trigger(:click) + + find('.hint.cvv') + + # fill out all billing info and account info + fill_in 'billing-first-name', with: 'Seth' + fill_in 'billing-last-name', with: 'Call' + fill_in 'billing-address1', with: '10704 Buckthorn Drive' + fill_in 'billing-city', with: 'Austin' + fill_in 'billing-state', with: 'Texas' + fill_in 'billing-zip', with: '78759' + fill_in 'card-number', with: '4111111111111111' + fill_in 'card-verify', with: '012' + + within('#checkout-payment-info') do + # fill in user/email/tos + fill_in 'email', with: 'bogus+gc1@jamkazam.com' + fill_in 'password', with: 'jam123' + end + + find('#divJamKazamTos ins.iCheck-helper').trigger(:click) # accept TOS + + # try to submit, and see order page + find('#payment-info-next').trigger(:click) + + find('.order-items-value.sub-total', text:'10.00') + find('.order-items-value.taxes', text:'0.83') + find('.order-items-value.order-total', text:'$10.00') + find('.order-items-value.grand-total', text:'$10.83') + + # click the ORDER button + find('.place-order-center a.button-orange.place-order').trigger(:click) + + # and now we should see confirmation, and a notice that we are in a normal browser + find('.thanks-detail.gift-card') + + created_user = User.find_by_email('bogus+gc1@jamkazam.com') + + sleep 3 # challenge to all comers! WHY DO I HAVE TO SLEEP FOR THIS ASSERTION TO BE TRUE! GAH . and 1 second won't do it + + created_user.reload + created_user.has_redeemable_jamtrack.should be_true + created_user.gifted_jamtracks.should eq(0) + created_user.gift_card_purchases.length.should eq(1) + + # verify sales data + created_user.sales.length.should eq(1) + sale = created_user.sales.last + sale.sale_line_items.length.should eq(1) + line_item = sale.sale_line_items[0] + line_item.product_type.should eq('GiftCardType') + line_item.product_id.should eq('jam_tracks_5') + line_item.quantity.should eq(1) + line_item.free.should eq(0) + line_item.unit_price.should eq(10.00) + line_item.sale.should eq(sale) + line_item.affiliate_referral.should eq(partner) + line_item.affiliate_refunded.should be_false + line_item.affiliate_refunded_at.should be_nil + line_item.affiliate_referral_fee_in_cents.should eq(10.00 * partner.rate * 100) + end + + it "logged out (10)" do + visit "/landing/gift-card" + + find('h1.jam-track-name', '$10 or $20 JAMTRACKS GIFT CARDS') + find('h2.original-artist', 'A PERFECT GIFT FOR THE HOLIDAYS') + jamtrack_rocketman.jam_track_tracks.each do |track| + if track.master? + find('.tracks.previews[data-id="' + track.id + '"] img.instrument-icon[data-instrument-id="other"]') + find('.tracks.previews[data-id="' + track.id + '"] .instrument-name', text: 'Master Mix') + else + find('.tracks.previews[data-id="' + track.id + '"] img.instrument-icon[data-instrument-id="' + track.instrument.id + '"]') + find('.tracks.previews[data-id="' + track.id + '"] .instrument-name', text: track.instrument.description) + end + end + find('a.browse-all')['href'].should eq("/client?search=#/jamtrack/search") + find('button.ten-jt', text: 'ADD $20 CARD TO CART').trigger(:click) + + # land in shopping cart first + find('.proceed-checkout').trigger(:click) + + # checkoutSignin page now + find('h3', text: 'NOT A MEMBER?') + + # hit 'NEXT' to create account and enter payment info + find('.btnNext.button-orange').trigger(:click) + + find('.hint.cvv') + + # fill out all billing info and account info + fill_in 'billing-first-name', with: 'Seth' + fill_in 'billing-last-name', with: 'Call' + fill_in 'billing-address1', with: '10704 Buckthorn Drive' + fill_in 'billing-city', with: 'Austin' + fill_in 'billing-state', with: 'Texas' + fill_in 'billing-zip', with: '78759' + fill_in 'card-number', with: '4111111111111111' + fill_in 'card-verify', with: '012' + + within('#checkout-payment-info') do + # fill in user/email/tos + fill_in 'email', with: 'bogus+gc2@jamkazam.com' + fill_in 'password', with: 'jam123' + end + + find('#divJamKazamTos ins.iCheck-helper').trigger(:click) # accept TOS + + # try to submit, and see order page + find('#payment-info-next').trigger(:click) + + find('.order-items-value.sub-total', text:'20.00') + find('.order-items-value.taxes', text:'1.65') + find('.order-items-value.order-total', text:'$20.00') + find('.order-items-value.grand-total', text:'$21.65') + + # click the ORDER button + find('.place-order-center a.button-orange.place-order').trigger(:click) + + # and now we should see confirmation, and a notice that we are in a normal browser + find('.thanks-detail.gift-card') + + created_user = User.find_by_email('bogus+gc2@jamkazam.com') + + sleep 3 # challenge to all comers! WHY DO I HAVE TO SLEEP FOR THIS ASSERTION TO BE TRUE! GAH . and 1 second won't do it + + created_user.reload + created_user.has_redeemable_jamtrack.should be_true + created_user.gifted_jamtracks.should eq(0) + created_user.gift_card_purchases.length.should eq(1) + + # verify sales data + created_user.sales.length.should eq(1) + sale = created_user.sales.last + sale.sale_line_items.length.should eq(1) + line_item = sale.sale_line_items[0] + line_item.product_type.should eq('GiftCardType') + line_item.product_id.should eq('jam_tracks_10') + line_item.quantity.should eq(1) + line_item.free.should eq(0) + line_item.unit_price.should eq(20.00) + line_item.sale.should eq(sale) + end + + it "logged in (5)" do + fast_signin(user,"/landing/gift-card") + + find('h1.jam-track-name', '$10 or $20 JAMTRACKS GIFT CARDS') + find('h2.original-artist', 'A PERFECT GIFT FOR THE HOLIDAYS') + jamtrack_rocketman.jam_track_tracks.each do |track| + if track.master? + find('.tracks.previews[data-id="' + track.id + '"] img.instrument-icon[data-instrument-id="other"]') + find('.tracks.previews[data-id="' + track.id + '"] .instrument-name', text: 'Master Mix') + else + find('.tracks.previews[data-id="' + track.id + '"] img.instrument-icon[data-instrument-id="' + track.instrument.id + '"]') + find('.tracks.previews[data-id="' + track.id + '"] .instrument-name', text: track.instrument.description) + end + end + find('a.browse-all')['href'].should eq("/client?search=#/jamtrack/search") + find('button.five-jt', text: 'ADD $10 CARD TO CART').trigger(:click) + + # land in shopping cart because we are a user; log in + find('.proceed-checkout').trigger(:click) + + find('h1', text: 'check out') + + # fill out all billing info and account info + fill_in 'billing-first-name', with: 'Seth' + fill_in 'billing-last-name', with: 'Call' + fill_in 'billing-address1', with: '10704 Buckthorn Drive' + fill_in 'billing-city', with: 'Austin' + fill_in 'billing-state', with: 'Texas' + fill_in 'billing-zip', with: '78759' + fill_in 'card-number', with: '4111111111111111' + fill_in 'card-verify', with: '012' + + # try to submit, and see order page + find('#payment-info-next').trigger(:click) + + find('.order-items-value.sub-total', text:'10.00') + find('.order-items-value.taxes', text:'0.83') + find('.order-items-value.order-total', text:'$10.00') + find('.order-items-value.grand-total', text:'$10.83') + + # click the ORDER button + find('.place-order-center a.button-orange.place-order').trigger(:click) + + # and now we should see confirmation, and a notice that we are in a normal browser + find('.thanks-detail.gift-card') + + user.reload + + sleep 3 # challenge to all comers! WHY DO I HAVE TO SLEEP FOR THIS ASSERTION TO BE TRUE! GAH . and 1 second won't do it + + user.reload + user.has_redeemable_jamtrack.should be_true + user.gifted_jamtracks.should eq(0) + user.gift_card_purchases.length.should eq(1) + end + +end diff --git a/web/spec/features/individual_jamtrack_spec.rb b/web/spec/features/individual_jamtrack_spec.rb index e914e4454..05ba964e8 100644 --- a/web/spec/features/individual_jamtrack_spec.rb +++ b/web/spec/features/individual_jamtrack_spec.rb @@ -94,8 +94,6 @@ describe "Individual JamTrack", :js => true, :type => :feature, :capybara_featur find('.tracks.previews[data-id="' + track.id + '"] .instrument-name', text:track.instrument.description) end end - find('a.cta-free-jamtrack').trigger(:click) - find('a.browse-all')['href'].should eq("/client?search=#/jamtrack/search") find('button.cta-button').trigger(:click) find('.browse-jamtracks', text: 'search jamtracks') diff --git a/web/spec/features/jamtrack_shopping_spec.rb b/web/spec/features/jamtrack_shopping_spec.rb index 30a4ad3e6..474a73f5b 100644 --- a/web/spec/features/jamtrack_shopping_spec.rb +++ b/web/spec/features/jamtrack_shopping_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe "JamTrack Shopping", :js => true, :type => :feature, :capybara_feature => true do - let(:user) { FactoryGirl.create(:user, has_redeemable_jamtrack: false) } + let(:user) { FactoryGirl.create(:user, gifted_jamtracks: 0, has_redeemable_jamtrack: false) } let(:jt_us) { FactoryGirl.create(:jam_track, :name=>'jt_us', sales_region: 'Worldwide', make_track: true, original_artist: "foobar") } let(:jt_ww) { FactoryGirl.create(:jam_track, :name=>'jt_ww', sales_region: 'Worldwide', make_track: true, original_artist: "barfoo") } let(:jt_rock) { FactoryGirl.create(:jam_track, :name=>'jt_rock', genres: [JamRuby::Genre.find('rock')], make_track: true, original_artist: "badfood") } diff --git a/web/spec/features/redeem_giftcard_spec.rb b/web/spec/features/redeem_giftcard_spec.rb new file mode 100644 index 000000000..631fb8e3a --- /dev/null +++ b/web/spec/features/redeem_giftcard_spec.rb @@ -0,0 +1,169 @@ +require 'spec_helper' + +# tests what happens when the websocket connection goes away +describe "Redeem Gift Card", :js => true, :type => :feature, :capybara_feature => true do + + subject { page } + + let(:user1) { FactoryGirl.create(:user) } + let(:jamtrack_acdc_backinblack) { @jamtrack_acdc_backinblack } + let(:gift_card) {FactoryGirl.create(:gift_card)} + + before(:all) do + User.delete_all + JamTrack.delete_all + + @jamtrack_acdc_backinblack = FactoryGirl.create(:jam_track, name: 'Back in Black', original_artist: 'AC/DC', sales_region: 'United States', make_track: true, plan_code: 'jamtrack-acdc-backinblack') + end + + describe "not logged in" do + it "suceeeds" do + visit '/redeem_giftcard' + + find('h2', text:'Redeem Your Gift Card') + fill_in "code", with: gift_card.code + fill_in "email", with: "gifter1@jamkazam.com" + fill_in "password", with: "jam123" + find('.redeem-container ins').trigger(:click) + + find('button.redeem-giftcard').trigger(:click) + + find('.done-action a.go-browse').trigger(:click) + + find('.free-jamtrack') + + user = User.find_by_email("gifter1@jamkazam.com") + gift_card.reload + gift_card.user.should eq(user) + user.reload + user.gifted_jamtracks.should eq(5) + end + + it "validates correctly" do + visit '/redeem_giftcard' + + find('h2', text:'Redeem Your Gift Card') + + find('button.redeem-giftcard').trigger(:click) + + find('.errors.active', text: "Email can't be blank") + + find('h2', text:'Redeem Your Gift Card') + fill_in "code", with: gift_card.code + fill_in "email", with: "gifter2@jamkazam.com" + fill_in "password", with: "jam123" + find('.redeem-container ins').trigger(:click) + + find('button.redeem-giftcard').trigger(:click) + + find('.done-action a.go-browse').trigger(:click) + + find('.free-jamtrack') + + user = User.find_by_email("gifter2@jamkazam.com") + gift_card.reload + gift_card.user.should eq(user) + user.reload + user.gifted_jamtracks.should eq(5) + end + + it "converts shopping cart items to free" do + + visit '/redeem_giftcard' + + anon_user_id = page.driver.cookies["user_uuid"] + anon_user = AnonymousUser.new(anon_user_id.value, {}) + + cart = ShoppingCart.add_jam_track_to_cart(anon_user, jamtrack_acdc_backinblack) + cart.skip_mix_check = true + cart.marked_for_redeem = 0 + cart.save! + + find('h2', text:'Redeem Your Gift Card') + fill_in "code", with: gift_card.code + fill_in "email", with: "gifter_carted1@jamkazam.com" + fill_in "password", with: "jam123" + find('.redeem-container ins').trigger(:click) + + find('button.redeem-giftcard').trigger(:click) + + find('.done-action a.go-browse').trigger(:click) + + find('.free-jamtrack') + + cart.reload + cart.marked_for_redeem.should eq(1) + + end + end + + describe "logged in" do + it "succeeds" do + fast_signin(user1, '/redeem_giftcard') + + find('h2', text:'Redeem Your Gift Card') + fill_in "code", with: gift_card.code + + find('button.redeem-giftcard').trigger(:click) + + find('.done-action a.go-browse').trigger(:click) + + find('.free-jamtrack') + + gift_card.reload + gift_card.user.should eq(user1) + user1.reload + user1.gifted_jamtracks.should eq(5) + end + end + + describe "logged in" do + it "validates" do + fast_signin(user1, '/redeem_giftcard') + + find('h2', text:'Redeem Your Gift Card') + + find('button.redeem-giftcard').trigger(:click) + + find('.errors.active', text: "Gift Card Code does not exist") + + fill_in "code", with: gift_card.code + + find('button.redeem-giftcard').trigger(:click) + + find('.done-action a.go-browse').trigger(:click) + + find('.free-jamtrack') + + gift_card.reload + gift_card.user.should eq(user1) + user1.reload + user1.gifted_jamtracks.should eq(5) + end + + it "converts shopping cart items to free" do + + fast_signin(user1, '/redeem_giftcard') + cart = ShoppingCart.add_jam_track_to_cart(user1, jamtrack_acdc_backinblack) + cart.skip_mix_check = true + cart.marked_for_redeem = 0 + cart.save! + + visit '/redeem_giftcard' + + find('h2', text:'Redeem Your Gift Card') + + fill_in "code", with: gift_card.code + find('button.redeem-giftcard').trigger(:click) + find('.done-action a.go-browse').trigger(:click) + find('.free-jamtrack') + + gift_card.reload + gift_card.user.should eq(user1) + user1.reload + user1.gifted_jamtracks.should eq(5) + cart.reload + cart.marked_for_redeem.should eq(1) + end + end +end diff --git a/web/spec/managers/user_manager_spec.rb b/web/spec/managers/user_manager_spec.rb index f6d464226..b0747dac4 100644 --- a/web/spec/managers/user_manager_spec.rb +++ b/web/spec/managers/user_manager_spec.rb @@ -707,4 +707,77 @@ describe UserManager do user.errors.any?.should be_false end # it "passes when facebook signup" end # describe "with nocaptcha" + + describe "gift_card" do + + let(:gift_card) {FactoryGirl.create(:gift_card)} + + it "can succeed when specified" do + user = @user_manager.signup(remote_ip: "1.2.3.4", + first_name: "bob", + last_name: "smith", + email: "giftcard1@jamkazam.com", + password: "foobar", + password_confirmation: "foobar", + terms_of_service: true, + instruments: @instruments, + musician: true, + location: @loca, + signup_confirm_url: "http://localhost:3000/confirm", + gift_card: gift_card.code) + user.errors.any?.should be_false + gift_card.reload + gift_card.user.should eq(user) + user = User.find(user.id) + user.has_redeemable_jamtrack.should be_true + user.gifted_jamtracks.should eq(5) + user.gift_cards[0].should eq(gift_card) + end + + it "will fail if invalid gift card code" do + user = @user_manager.signup(remote_ip: "1.2.3.4", + first_name: "bob", + last_name: "smith", + email: "giftcard2@jamkazam.com", + password: "foobar", + password_confirmation: "foobar", + terms_of_service: true, + instruments: @instruments, + musician: true, + location: @loca, + signup_confirm_url: "http://localhost:3000/confirm", + gift_card: '') + user.errors.any?.should be_true + user.errors["gift_card"].should eq(["not found"]) + user.gifted_jamtracks.should eq(0) + gift_card.reload + gift_card.user.should be_nil + + user.gift_cards.length.should eq(0) + end + + it "will fail if used gift card" do + gift_card.user = FactoryGirl.create(:user) + + user = @user_manager.signup(remote_ip: "1.2.3.4", + first_name: "bob", + last_name: "smith", + email: "giftcard2@jamkazam.com", + password: "foobar", + password_confirmation: "foobar", + terms_of_service: true, + instruments: @instruments, + musician: true, + location: @loca, + signup_confirm_url: "http://localhost:3000/confirm", + gift_card: '') + user.errors.any?.should be_true + user.errors["gift_card"].should eq(["not found"]) + user.gifted_jamtracks.should eq(0) + gift_card.reload + gift_card.user.should be_nil + + user.gift_cards.length.should eq(0) + end + end end # test diff --git a/web/spec/requests/active_music_sessions_api_spec.rb b/web/spec/requests/active_music_sessions_api_spec.rb index af4f9a5b6..8624110c0 100755 --- a/web/spec/requests/active_music_sessions_api_spec.rb +++ b/web/spec/requests/active_music_sessions_api_spec.rb @@ -627,7 +627,7 @@ describe "Active Music Session API ", :type => :api do music_session = JSON.parse(last_response.body)[0] # start a recording - post "/api/recordings/start", {:format => :json, :music_session_id => music_session['id'] }.to_json, "CONTENT_TYPE" => 'application/json' + post "/api/recordings/start", {:format => :json, :music_session_id => music_session['id'], record_video:false }.to_json, "CONTENT_TYPE" => 'application/json' last_response.status.should eql(201) # user 2 should not be able to join