* merge
This commit is contained in:
commit
2864b751f7
|
|
@ -2,48 +2,47 @@ ActiveAdmin.register JamRuby::AffiliatePartner, :as => 'Affiliates' do
|
|||
|
||||
menu :label => 'Partners', :parent => 'Affiliates'
|
||||
|
||||
config.sort_order = 'created_at DESC'
|
||||
config.sort_order = 'referral_user_count DESC'
|
||||
config.batch_actions = false
|
||||
# config.clear_action_items!
|
||||
config.filters = false
|
||||
|
||||
form :partial => 'form'
|
||||
|
||||
scope("Active", default: true) { |scope| scope.where('partner_user_id IS NOT NULL') }
|
||||
scope("Unpaid") { |partner| partner.unpaid }
|
||||
|
||||
index do
|
||||
column 'User' do |oo| link_to(oo.partner_user.name, "http://www.jamkazam.com/client#/profile/#{oo.partner_user.id}", {:title => oo.partner_user.name}) end
|
||||
column 'Email' do |oo| oo.partner_user.email end
|
||||
|
||||
# default_actions # use this for all view/edit/delete links
|
||||
|
||||
column 'User' do |oo| link_to(oo.partner_user.name, admin_user_path(oo.partner_user.id), {:title => oo.partner_user.name}) end
|
||||
column 'Name' do |oo| oo.partner_name end
|
||||
column 'Code' do |oo| oo.partner_code end
|
||||
column 'Type' do |oo| oo.entity_type end
|
||||
column 'Code' do |oo| oo.id end
|
||||
column 'Referral Count' do |oo| oo.referral_user_count end
|
||||
# column 'Referrals' do |oo| link_to('View', admin_referrals_path(AffiliatePartner::PARAM_REFERRAL => oo.id)) end
|
||||
column 'Earnings' do |oo| sprintf("$%.2f", oo.cumulative_earnings_in_dollars) end
|
||||
column 'Amount Owed' do |oo| sprintf("$%.2f", oo.due_amount_in_cents.to_f / 100.to_f) end
|
||||
column 'Pay Actions' do |oo|
|
||||
link_to('Mark Paid', mark_paid_admin_affiliate_path(oo.id), :confirm => "Mark this affiliate as PAID?") if oo.unpaid
|
||||
end
|
||||
|
||||
default_actions
|
||||
end
|
||||
|
||||
|
||||
action_item :only => [:show] do
|
||||
link_to("Mark Paid",
|
||||
mark_paid_admin_affiliate_path(resource.id),
|
||||
:confirm => "Mark this affiliate as PAID?") if resource.unpaid
|
||||
end
|
||||
|
||||
member_action :mark_paid, :method => :get do
|
||||
resource.mark_paid
|
||||
redirect_to admin_affiliate_path(resource.id)
|
||||
end
|
||||
|
||||
controller do
|
||||
|
||||
def show
|
||||
redirect_to admin_referrals_path(AffiliatePartner::PARAM_REFERRAL => resource.id)
|
||||
end
|
||||
|
||||
def create
|
||||
obj = AffiliatePartner.create_with_params(params[:jam_ruby_affiliate_partner])
|
||||
if obj.errors.present?
|
||||
set_resource_ivar(obj)
|
||||
render active_admin_template('new')
|
||||
else
|
||||
redirect_to admin_affiliates_path
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
obj = resource
|
||||
vals = params[:jam_ruby_affiliate_partner]
|
||||
obj.partner_name = vals[:partner_name]
|
||||
obj.user_email = vals[:user_email] if vals[:user_email].present?
|
||||
obj.save!
|
||||
set_resource_ivar(obj)
|
||||
render active_admin_template('show')
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@ ActiveAdmin.register_page "Fake Purchaser" do
|
|||
|
||||
page_action :bulk_jamtrack_purchase, :method => :post do
|
||||
|
||||
puts params.inspect
|
||||
|
||||
user_field = params[:jam_ruby_jam_track_right][:user]
|
||||
|
||||
if user_field.blank?
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
ActiveAdmin.register JamRuby::FraudAlert, :as => 'Fraud Alerts' do
|
||||
|
||||
menu :label => 'Fraud Alerts', :parent => 'JamTracks'
|
||||
|
||||
config.sort_order = 'created_at desc'
|
||||
config.batch_actions = false
|
||||
|
||||
|
||||
scope("Not Whitelisted", default:true) { |scope|
|
||||
scope.joins('INNER JOIN "machine_fingerprints" ON "machine_fingerprints"."id" = "fraud_alerts"."machine_fingerprint_id" LEFT OUTER JOIN "fingerprint_whitelists" ON "fingerprint_whitelists"."fingerprint" = "machine_fingerprints"."fingerprint"').where('fingerprint_whitelists IS NULL')}
|
||||
|
||||
index do
|
||||
default_actions
|
||||
|
||||
column :machine_fingerprint
|
||||
column :user
|
||||
column :created_at
|
||||
column :resolved
|
||||
|
||||
column "" do |alert|
|
||||
link_to 'Matching MAC', "fraud_alerts/#{alert.id}/same_fingerprints"
|
||||
end
|
||||
column "" do |alert|
|
||||
link_to 'Matching MAC and IP Address', "fraud_alerts/#{alert.id}/same_fingerprints_and_ip"
|
||||
end
|
||||
column "" do |alert|
|
||||
link_to 'Matching IP Address', "fraud_alerts/#{alert.id}/same_ip"
|
||||
end
|
||||
column "" do |alert|
|
||||
link_to 'Resolve', "fraud_alerts/#{alert.id}/resolve"
|
||||
end
|
||||
column "" do |alert|
|
||||
link_to 'Whitelist Similar', "fraud_alerts/#{alert.id}/whitelist"
|
||||
end
|
||||
end
|
||||
|
||||
member_action :same_fingerprints, :method => :get do
|
||||
alert = FraudAlert.find(params[:id])
|
||||
|
||||
redirect_to admin_machine_fingerprints_path("q[fingerprint_equals]" => alert.machine_fingerprint.fingerprint, commit: 'Filter', order: 'created_at_desc')
|
||||
end
|
||||
|
||||
member_action :same_fingerprints_and_ip, :method => :get do
|
||||
alert = FraudAlert.find(params[:id])
|
||||
|
||||
redirect_to admin_machine_fingerprints_path("q[fingerprint_equals]" => alert.machine_fingerprint.fingerprint, "q[remote_ip_equals]" => alert.machine_fingerprint.remote_ip, commit: 'Filter', order: 'created_at_desc')
|
||||
end
|
||||
|
||||
|
||||
member_action :resolve, :method => :get do
|
||||
alert = FraudAlert.find(params[:id])
|
||||
alert.resolved = true
|
||||
alert.save!
|
||||
|
||||
redirect_to admin_fraud_alerts_path, notice: "That fraud alert has been marked as resolved"
|
||||
end
|
||||
|
||||
member_action :whitelist, :method => :get do
|
||||
alert = FraudAlert.find(params[:id])
|
||||
|
||||
wl = FingerprintWhitelist.new
|
||||
wl.fingerprint = alert.machine_fingerprint.fingerprint
|
||||
success = wl.save
|
||||
|
||||
redirect_to admin_fraud_alerts_path, notice: success ? "Added #{alert.machine_fingerprint.fingerprint} to whitelist" : "Could not add #{alert.machine_fingerprint.fingerprint} to whiteliste"
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
|
@ -32,6 +32,7 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do
|
|||
row :birth_date
|
||||
row :gender
|
||||
row :email_confirmed
|
||||
row :remember_token
|
||||
row :image do user.photo_url ? image_tag(user.photo_url) : '' end
|
||||
end
|
||||
active_admin_comments
|
||||
|
|
|
|||
|
|
@ -1,20 +1,29 @@
|
|||
require 'jam_ruby/recurly_client'
|
||||
ActiveAdmin.register JamRuby::JamTrackRight, :as => 'JamTrackRights' do
|
||||
|
||||
menu :label => 'Purchased JamTracks', :parent => 'JamTracks'
|
||||
menu :label => 'Purchased JamTracks', :parent => 'Purchases'
|
||||
|
||||
config.sort_order = 'updated_at DESC'
|
||||
config.sort_order = 'created_at DESC'
|
||||
config.batch_actions = false
|
||||
|
||||
#form :partial => 'form'
|
||||
|
||||
filter :user_id
|
||||
|
||||
filter :user_id,
|
||||
:label => "USER ID", :required => false,
|
||||
:wrapper_html => { :style => "list-style: none" }
|
||||
|
||||
|
||||
filter :jam_track
|
||||
|
||||
index do
|
||||
default_actions
|
||||
|
||||
column "Order" do |right|
|
||||
link_to("Place", order_admin_jam_track_right_path(right)) + " | " +
|
||||
link_to("Refund", refund_admin_jam_track_right_path(right))
|
||||
end
|
||||
#column "Order" do |right|
|
||||
#link_to("Place", order_admin_jam_track_right_path(right)) + " | " +
|
||||
# link_to("Refund", refund_admin_jam_track_right_path(right))
|
||||
#end
|
||||
|
||||
column "Last Name" do |right|
|
||||
right.user.last_name
|
||||
|
|
@ -23,13 +32,15 @@ ActiveAdmin.register JamRuby::JamTrackRight, :as => 'JamTrackRights' do
|
|||
right.user.first_name
|
||||
end
|
||||
column "Jam Track" do |right|
|
||||
link_to(right.jam_track.name, admin_jam_track_right_path(right.jam_track))
|
||||
link_to(right.jam_track.name, admin_jam_track_path(right.jam_track))
|
||||
# right.jam_track
|
||||
end
|
||||
column "Plan Code" do |right|
|
||||
|
||||
right.jam_track.plan_code
|
||||
end
|
||||
column "Redeemed" do |right|
|
||||
right.redeemed ? 'Y' : 'N'
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
|
@ -42,6 +53,9 @@ ActiveAdmin.register JamRuby::JamTrackRight, :as => 'JamTrackRights' do
|
|||
f.actions
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
|
||||
member_action :order, :method => :get do
|
||||
right = JamTrackRight.where("id=?",params[:id]).first
|
||||
user = right.user
|
||||
|
|
@ -84,4 +98,5 @@ ActiveAdmin.register JamRuby::JamTrackRight, :as => 'JamTrackRights' do
|
|||
redirect_to admin_jam_track_rights_path, notice: "Issued full refund on #{right.jam_track} for #{right.user.to_s}"
|
||||
end
|
||||
end
|
||||
=end
|
||||
end
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
ActiveAdmin.register JamRuby::MachineExtra, :as => 'Machine Extra' do
|
||||
|
||||
menu :label => 'Machine Extra', :parent => 'JamTracks'
|
||||
|
||||
config.sort_order = 'created_at desc'
|
||||
config.batch_actions = false
|
||||
|
||||
end
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
ActiveAdmin.register JamRuby::MachineFingerprint, :as => 'Machine Fingerprints' do
|
||||
|
||||
menu :label => 'Machine Fingerprints', :parent => 'JamTracks'
|
||||
|
||||
config.sort_order = 'created_at desc'
|
||||
config.batch_actions = false
|
||||
|
||||
index do
|
||||
column :user
|
||||
column 'Hash' do |fp|
|
||||
fp.fingerprint
|
||||
end
|
||||
column :remote_ip
|
||||
column 'Detail' do |fp|
|
||||
detail = fp.detail
|
||||
if detail
|
||||
detail.to_s
|
||||
end
|
||||
end
|
||||
column :created_at
|
||||
end
|
||||
end
|
||||
|
|
@ -15,6 +15,7 @@ ActiveAdmin.register JamRuby::Mix, :as => 'Mixes' do
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
index :as => :block do |mix|
|
||||
div :for => mix do
|
||||
h3 "Mix (Users: #{mix.recording.users.map { |u| u.name }.join ','}) (When: #{mix.created_at.strftime('%b %d %Y, %H:%M')})"
|
||||
|
|
@ -26,6 +27,16 @@ ActiveAdmin.register JamRuby::Mix, :as => 'Mixes' do
|
|||
row :created_at do |mix| mix.created_at.strftime('%b %d %Y, %H:%M') end
|
||||
row :s3_url do |mix| mix.sign_url end
|
||||
row :manifest do |mix| mix.manifest end
|
||||
row :local_manifest do |mix|
|
||||
div class: 'local-manifest' do
|
||||
mix.local_manifest.to_json
|
||||
end
|
||||
end
|
||||
row :download_script do |mix|
|
||||
div class: 'download-script' do
|
||||
mix.download_script
|
||||
end
|
||||
end
|
||||
row :completed do |mix| "#{mix.completed ? "finished" : "not finished"}" end
|
||||
if mix.completed
|
||||
row :completed_at do |mix| mix.completed_at.strftime('%b %d %Y, %H:%M') end
|
||||
|
|
@ -39,6 +50,7 @@ ActiveAdmin.register JamRuby::Mix, :as => 'Mixes' do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
ActiveAdmin.register JamRuby::MusicSessionComment, :as => 'Ratings' do
|
||||
|
||||
config.per_page = 150
|
||||
config.clear_action_items!
|
||||
config.sort_order = 'created_at_desc'
|
||||
menu :parent => 'Sessions', :label => 'Ratings'
|
||||
|
||||
index do
|
||||
column :comment
|
||||
column :user
|
||||
column :created_at
|
||||
end
|
||||
end
|
||||
|
|
@ -2,12 +2,9 @@ ActiveAdmin.register_page "Recurly Health" do
|
|||
menu :parent => 'Misc'
|
||||
|
||||
content :title => "Recurly Transaction Totals" do
|
||||
table_for Sale.check_integrity do
|
||||
table_for Sale.check_integrity_of_jam_track_sales do
|
||||
column "Total", :total
|
||||
column "Unknown", :not_known
|
||||
column "Successes", :succeeded
|
||||
column "Failures", :failed
|
||||
column "Refunds", :refunded
|
||||
column "Voids", :voided
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
ActiveAdmin.register JamRuby::RecurlyTransactionWebHook, :as => 'RecurlyHooks' do
|
||||
|
||||
menu :label => 'Recurly Transaction Hooks', :parent => 'Purchases'
|
||||
|
||||
config.sort_order = 'created_at DESC'
|
||||
config.batch_actions = false
|
||||
|
||||
actions :all, :except => [:destroy]
|
||||
|
||||
#form :partial => 'form'
|
||||
|
||||
|
||||
filter :transaction_type, :as => :select, :collection => JamRuby::RecurlyTransactionWebHook::HOOK_TYPES
|
||||
filter :user_id,
|
||||
:label => "USER ID", :required => false,
|
||||
:wrapper_html => { :style => "list-style: none" }
|
||||
|
||||
filter :invoice_id
|
||||
|
||||
form :partial => 'form'
|
||||
|
||||
index do
|
||||
|
||||
default_actions
|
||||
|
||||
column :transaction_type
|
||||
column :transaction_at
|
||||
column :amount_in_cents
|
||||
column 'Transaction' do |hook| link_to('Go to Recurly', Rails.application.config.recurly_root_url + "/transactions/#{hook.recurly_transaction_id}") end
|
||||
column 'Invoice' do |hook| link_to(hook.invoice_number, Rails.application.config.recurly_root_url + "/invoices/#{hook.invoice_number}") end
|
||||
column :admin_description
|
||||
column 'User' do |hook| link_to("#{hook.user.email} (#{hook.user.name})", admin_user_path(hook.user.id)) end
|
||||
|
||||
#column "Order" do |right|
|
||||
#link_to("Place", order_admin_jam_track_right_path(right)) + " | " +
|
||||
# link_to("Refund", refund_admin_jam_track_right_path(right))
|
||||
#end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
ActiveAdmin.register JamRuby::SessionInfoComment, :as => 'Comments' do
|
||||
|
||||
config.per_page = 50
|
||||
config.clear_action_items!
|
||||
config.sort_order = 'created_at_desc'
|
||||
menu :parent => 'Sessions', :label => 'Comments'
|
||||
|
||||
end
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
ActiveAdmin.register_page "Test Jobs" do
|
||||
menu :parent => 'Misc'
|
||||
|
||||
page_action :long_running, :method => :post do
|
||||
|
||||
puts params.inspect
|
||||
|
||||
time = params[:long_running][:time]
|
||||
|
||||
Resque.enqueue(LongRunning, time)
|
||||
|
||||
redirect_to admin_test_jobs_path, :notice => "Long Running job enqueued."
|
||||
end
|
||||
|
||||
|
||||
content do
|
||||
|
||||
semantic_form_for LongRunning.new, :url => admin_test_jobs_long_running_path, :builder => ActiveAdmin::FormBuilder do |f|
|
||||
f.inputs "Queue a long running job" do
|
||||
f.input :time
|
||||
end
|
||||
f.actions
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -22,4 +22,11 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.download-script, .local-manifest {
|
||||
white-space:pre-wrap;
|
||||
background-color:white;
|
||||
border:1px solid gray;
|
||||
padding:5px;
|
||||
}
|
||||
|
|
@ -55,7 +55,8 @@ class Cohort < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def self.earliest_cohort
|
||||
starting = User.where(admin: false).order(:created_at).first.created_at
|
||||
user = User.where(admin: false).order(:created_at).first
|
||||
starting = user.created_at if user
|
||||
starting = EARLIEST_DATE if starting.nil? || starting < EARLIEST_DATE
|
||||
starting # this is necessary to always return not null
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,13 +1,7 @@
|
|||
<%= semantic_form_for([:admin, resource], :url => resource.new_record? ? admin_affiliates_path : "/admin/affiliates/#{resource.id}") do |f| %>
|
||||
<%= f.semantic_errors *f.object.errors.keys %>
|
||||
<%= f.inputs do %>
|
||||
<%= f.input(:user_email, :input_html => {:maxlength => 255}) %>
|
||||
<%= f.input(:partner_name, :input_html => {:maxlength => 128}) %>
|
||||
<% if resource.new_record? %>
|
||||
<%= f.input(:partner_code, :input_html => {:maxlength => 128}) %>
|
||||
<% else %>
|
||||
<%= f.input(:partner_code, :input_html => {:maxlength => 128, :readonly => 'readonly'}) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<%= f.actions %>
|
||||
<% end %>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
= semantic_form_for([:admin, resource], :html => {:multipart => true}, :url => resource.new_record? ? admin_recurly_transaction_web_hooks_path : "#{ENV['RAILS_RELATIVE_URL_ROOT']}/admin/recurly_hooks/#{resource.id}") do |f|
|
||||
= f.semantic_errors *f.object.errors.keys
|
||||
= f.inputs name: 'Recurly Web Hook fields' do
|
||||
= f.input :admin_description, :input_html => { :rows=>1, :maxlength=>200, }, hint: "this will display on the user's payment history page"
|
||||
= f.input :jam_track, collection: JamRuby::JamTrack.all, include_blank: true, hint: "Please indicate which JamTrack this refund for, if not set"
|
||||
= f.actions
|
||||
|
|
@ -83,6 +83,7 @@ module JamAdmin
|
|||
config.external_port = ENV['EXTERNAL_PORT'] || 3000
|
||||
config.external_protocol = ENV['EXTERNAL_PROTOCOL'] || 'http://'
|
||||
config.external_root_url = "#{config.external_protocol}#{config.external_hostname}#{(config.external_port == 80 || config.external_port == 443) ? '' : ':' + config.external_port.to_s}"
|
||||
config.recurly_root_url = 'https://jamkazam-development.recurly.com'
|
||||
|
||||
# where is rabbitmq?
|
||||
config.rabbitmq_host = "localhost"
|
||||
|
|
@ -116,7 +117,7 @@ module JamAdmin
|
|||
config.email_smtp_domain = 'www.jamkazam.com'
|
||||
config.email_smtp_authentication = :plain
|
||||
config.email_smtp_user_name = 'jamkazam'
|
||||
config.email_smtp_password = 'jamjamblueberryjam'
|
||||
config.email_smtp_password = 'snorkeltoesniffyfarce1'
|
||||
config.email_smtp_starttls_auto = true
|
||||
|
||||
config.facebook_app_id = ENV['FACEBOOK_APP_ID'] || '468555793186398'
|
||||
|
|
|
|||
|
|
@ -43,4 +43,7 @@ JamAdmin::Application.configure do
|
|||
|
||||
# Show the logging configuration on STDOUT
|
||||
config.show_log_configuration = true
|
||||
|
||||
config.email_generic_from = 'nobody-dev@jamkazam.com'
|
||||
config.email_alerts_alias = 'alerts-dev@jamkazam.com'
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@ class JamRuby::JamTrackTrack
|
|||
|
||||
# add a custom validation
|
||||
|
||||
attr_accessor :preview_generate_error
|
||||
|
||||
validate :preview
|
||||
|
||||
def preview
|
||||
|
|
@ -52,87 +50,4 @@ class JamRuby::JamTrackTrack
|
|||
end
|
||||
end
|
||||
|
||||
def generate_preview
|
||||
|
||||
begin
|
||||
Dir.mktmpdir do |tmp_dir|
|
||||
|
||||
input = File.join(tmp_dir, 'in.ogg')
|
||||
output = File.join(tmp_dir, 'out.ogg')
|
||||
output_mp3 = File.join(tmp_dir, 'out.mp3')
|
||||
|
||||
start = self.preview_start_time.to_f / 1000
|
||||
stop = start + 20
|
||||
|
||||
raise 'no track' unless self["url_44"]
|
||||
|
||||
s3_manager.download(self.url_by_sample_rate(44), input)
|
||||
|
||||
command = "sox \"#{input}\" \"#{output}\" trim #{sprintf("%.3f", start)} =#{sprintf("%.3f", stop)}"
|
||||
|
||||
@@log.debug("trimming using: " + command)
|
||||
|
||||
sox_output = `#{command}`
|
||||
|
||||
result_code = $?.to_i
|
||||
|
||||
if result_code != 0
|
||||
@@log.debug("fail #{result_code}")
|
||||
@preview_generate_error = "unable to execute cut command #{sox_output}"
|
||||
else
|
||||
# now create mp3 off of ogg preview
|
||||
|
||||
convert_mp3_cmd = "#{APP_CONFIG.ffmpeg_path} -i \"#{output}\" -ab 192k \"#{output_mp3}\""
|
||||
|
||||
@@log.debug("converting to mp3 using: " + convert_mp3_cmd)
|
||||
|
||||
convert_output = `#{convert_mp3_cmd}`
|
||||
|
||||
result_code = $?.to_i
|
||||
|
||||
if result_code != 0
|
||||
@@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.base64)
|
||||
|
||||
self.skip_uploader = true
|
||||
|
||||
original_ogg_preview_url = self["preview_url"]
|
||||
original_mp3_preview_url = self["preview_mp3_url"]
|
||||
|
||||
# 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 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"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue Exception => e
|
||||
@@log.error("error in sox command #{e.to_s}")
|
||||
@preview_generate_error = e.to_s
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
14
db/manifest
14
db/manifest
|
|
@ -274,4 +274,16 @@ recording_client_metadata.sql
|
|||
preview_support_mp3.sql
|
||||
jam_track_duration.sql
|
||||
sales.sql
|
||||
broadcast_notifications.sql
|
||||
show_whats_next_count.sql
|
||||
recurly_adjustments.sql
|
||||
signup_hints.sql
|
||||
packaging_notices.sql
|
||||
first_played_jamtrack_at.sql
|
||||
payment_history.sql
|
||||
jam_track_right_private_key.sql
|
||||
first_downloaded_jamtrack_at.sql
|
||||
signing.sql
|
||||
optimized_redeemption.sql
|
||||
optimized_redemption_warn_mode.sql
|
||||
affiliate_partners2.sql
|
||||
broadcast_notifications.sql
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
CREATE TABLE affiliate_legalese (
|
||||
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
legalese TEXT,
|
||||
version INTEGER NOT NULL DEFAULT 1,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
ALTER TABLE users DROP CONSTRAINT users_affiliate_referral_id_fkey;
|
||||
ALTER TABLE users DROP COLUMN affiliate_referral_id;
|
||||
DROP TABLE affiliate_partners;
|
||||
|
||||
CREATE TABLE affiliate_partners (
|
||||
id INTEGER PRIMARY KEY,
|
||||
partner_name VARCHAR(1000),
|
||||
partner_user_id VARCHAR(64) REFERENCES users(id) ON DELETE SET NULL,
|
||||
entity_type VARCHAR(64),
|
||||
legalese_id VARCHAR(64),
|
||||
signed_at TIMESTAMP,
|
||||
last_paid_at TIMESTAMP,
|
||||
address JSON NOT NULL DEFAULT '{}',
|
||||
tax_identifier VARCHAR(1000),
|
||||
referral_user_count INTEGER NOT NULL DEFAULT 0,
|
||||
cumulative_earnings_in_cents INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE SEQUENCE partner_key_sequence;
|
||||
ALTER SEQUENCE partner_key_sequence RESTART WITH 10000;
|
||||
ALTER TABLE affiliate_partners ALTER COLUMN id SET DEFAULT nextval('partner_key_sequence');;
|
||||
|
||||
ALTER TABLE users ADD COLUMN affiliate_referral_id INTEGER REFERENCES affiliate_partners(id) ON DELETE SET NULL;
|
||||
CREATE INDEX affiliate_partners_legalese_idx ON affiliate_partners(legalese_id);
|
||||
|
||||
CREATE UNLOGGED TABLE affiliate_referral_visits (
|
||||
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
affiliate_partner_id INTEGER NOT NULL,
|
||||
ip_address VARCHAR NOT NULL,
|
||||
visited_url VARCHAR,
|
||||
referral_url VARCHAR,
|
||||
first_visit BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
user_id VARCHAR(64),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX on affiliate_referral_visits (affiliate_partner_id, created_at);
|
||||
|
||||
CREATE TABLE affiliate_quarterly_payments (
|
||||
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
quarter INTEGER NOT NULL,
|
||||
year INTEGER NOT NULL,
|
||||
affiliate_partner_id INTEGER NOT NULL REFERENCES affiliate_partners(id),
|
||||
due_amount_in_cents INTEGER NOT NULL DEFAULT 0,
|
||||
paid BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
closed BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
jamtracks_sold INTEGER NOT NULL DEFAULT 0,
|
||||
closed_at TIMESTAMP,
|
||||
paid_at TIMESTAMP,
|
||||
last_updated TIMESTAMP,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE affiliate_monthly_payments (
|
||||
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
month INTEGER NOT NULL,
|
||||
year INTEGER NOT NULL,
|
||||
affiliate_partner_id INTEGER NOT NULL REFERENCES affiliate_partners(id),
|
||||
due_amount_in_cents INTEGER NOT NULL DEFAULT 0,
|
||||
closed BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
jamtracks_sold INTEGER NOT NULL DEFAULT 0,
|
||||
closed_at TIMESTAMP,
|
||||
last_updated TIMESTAMP,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
|
||||
CREATE INDEX ON affiliate_quarterly_payments (affiliate_partner_id, year, quarter);
|
||||
CREATE UNIQUE INDEX ON affiliate_quarterly_payments (year, quarter, affiliate_partner_id);
|
||||
CREATE UNIQUE INDEX ON affiliate_monthly_payments (year, month, affiliate_partner_id);
|
||||
CREATE INDEX ON affiliate_monthly_payments (affiliate_partner_id, year, month);
|
||||
|
||||
|
||||
ALTER TABLE sale_line_items ADD COLUMN affiliate_referral_id INTEGER REFERENCES affiliate_partners(id);
|
||||
ALTER TABLE sale_line_items ADD COLUMN affiliate_referral_fee_in_cents INTEGER;
|
||||
ALTER TABLE sale_line_items ADD COLUMN affiliate_refunded BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
ALTER TABLE sale_line_items ADD COLUMN affiliate_refunded_at TIMESTAMP;
|
||||
ALTER TABLE generic_state ADD COLUMN affiliate_tallied_at TIMESTAMP;
|
||||
|
||||
CREATE TABLE affiliate_traffic_totals (
|
||||
day DATE NOT NULL,
|
||||
signups INTEGER NOT NULL DEFAULT 0,
|
||||
visits INTEGER NOT NULL DEFAULT 0,
|
||||
affiliate_partner_id INTEGER NOT NULL REFERENCES affiliate_partners(id),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX ON affiliate_traffic_totals (day, affiliate_partner_id);
|
||||
CREATE INDEX ON affiliate_traffic_totals (affiliate_partner_id, day);
|
||||
|
||||
CREATE VIEW affiliate_payments AS
|
||||
SELECT id AS monthly_id,
|
||||
CAST(NULL as VARCHAR) AS quarterly_id,
|
||||
affiliate_partner_id,
|
||||
due_amount_in_cents,
|
||||
jamtracks_sold,
|
||||
created_at,
|
||||
closed,
|
||||
CAST(NULL AS BOOLEAN) AS paid,
|
||||
year,
|
||||
month as month,
|
||||
CAST(NULL AS INTEGER) as quarter,
|
||||
month as time_sort,
|
||||
'monthly' AS payment_type
|
||||
FROM affiliate_monthly_payments
|
||||
UNION ALL
|
||||
SELECT CAST(NULL as VARCHAR) AS monthly_id,
|
||||
id AS quarterly_id,
|
||||
affiliate_partner_id,
|
||||
due_amount_in_cents,
|
||||
jamtracks_sold,
|
||||
created_at,
|
||||
closed,
|
||||
paid,
|
||||
year,
|
||||
CAST(NULL AS INTEGER) as month,
|
||||
quarter,
|
||||
(quarter * 3) + 3 as time_sort,
|
||||
'quarterly' AS payment_type
|
||||
FROM affiliate_quarterly_payments;
|
||||
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE jam_track_rights ADD COLUMN first_downloaded_at TIMESTAMP;
|
||||
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE users ADD COLUMN first_played_jamtrack_at TIMESTAMP;
|
||||
|
|
@ -1 +1,9 @@
|
|||
ALTER TABLE jam_tracks ADD COLUMN duration INTEGER;
|
||||
DO $$
|
||||
BEGIN
|
||||
BEGIN
|
||||
ALTER TABLE jam_tracks ADD COLUMN duration INTEGER;
|
||||
EXCEPTION
|
||||
WHEN duplicate_column THEN RAISE NOTICE 'column duration already exists in jam_tracks.';
|
||||
END;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
ALTER TABLE jam_track_rights ADD COLUMN private_key_44 VARCHAR;
|
||||
ALTER TABLE jam_track_rights ADD COLUMN private_key_48 VARCHAR;
|
||||
ALTER TABLE jam_track_rights ADD COLUMN signed_48 BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
ALTER TABLE jam_track_rights ADD COLUMN signed_44 BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
ALTER TABLE jam_track_rights DROP COLUMN private_key;
|
||||
ALTER TABLE jam_track_rights DROP COLUMN signed;
|
||||
ALTER TABLE jam_track_rights ADD COLUMN signing_started_at_44 TIMESTAMP;
|
||||
ALTER TABLE jam_track_rights ADD COLUMN signing_started_at_48 TIMESTAMP;
|
||||
ALTER TABLE jam_track_rights DROP COLUMN signing_started_at;
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
CREATE TABLE machine_fingerprints (
|
||||
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id VARCHAR(64) NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
fingerprint VARCHAR(20000) NOT NULL UNIQUE,
|
||||
when_taken VARCHAR NOT NULL,
|
||||
print_type VARCHAR NOT NULL,
|
||||
remote_ip VARCHAR(1000) NOT NULL,
|
||||
jam_track_right_id BIGINT REFERENCES jam_track_rights(id) ON DELETE SET NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
ALTER TABLE jam_track_rights ADD COLUMN redeemed_and_fingerprinted BOOLEAN DEFAULT FALSE;
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
DROP TABLE machine_fingerprints;
|
||||
|
||||
CREATE TABLE machine_fingerprints (
|
||||
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id VARCHAR(64) NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
fingerprint VARCHAR(20000) NOT NULL,
|
||||
when_taken VARCHAR NOT NULL,
|
||||
print_type VARCHAR NOT NULL,
|
||||
remote_ip VARCHAR(1000) NOT NULL,
|
||||
jam_track_right_id BIGINT REFERENCES jam_track_rights(id) ON DELETE SET NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE machine_extras (
|
||||
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
machine_fingerprint_id VARCHAR(64) NOT NULL REFERENCES machine_fingerprints(id) ON DELETE CASCADE,
|
||||
mac_address VARCHAR(100),
|
||||
mac_name VARCHAR(255),
|
||||
upstate BOOLEAN,
|
||||
ipaddr_0 VARCHAR(200),
|
||||
ipaddr_1 VARCHAR(200),
|
||||
ipaddr_2 VARCHAR(200),
|
||||
ipaddr_3 VARCHAR(200),
|
||||
ipaddr_4 VARCHAR(200),
|
||||
ipaddr_5 VARCHAR(200),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE fraud_alerts (
|
||||
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
machine_fingerprint_id VARCHAR(64) NOT NULL REFERENCES machine_fingerprints(id) ON DELETE CASCADE,
|
||||
user_id VARCHAR(64) NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
resolved BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE fingerprint_whitelists (
|
||||
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
fingerprint VARCHAR(20000) UNIQUE NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX machine_fingerprints_index1 ON machine_fingerprints USING btree (fingerprint, user_id, remote_ip, created_at);
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
ALTER TABLE jam_track_rights ADD COLUMN packaging_steps INTEGER;
|
||||
ALTER TABLE jam_track_rights ADD COLUMN current_packaging_step INTEGER;
|
||||
ALTER TABLE jam_track_rights ADD COLUMN last_step_at TIMESTAMP;
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
ALTER TABLE recurly_transaction_web_hooks ADD COLUMN admin_description VARCHAR;
|
||||
ALTER TABLE recurly_transaction_web_hooks ADD COLUMN jam_track_id VARCHAR(64) REFERENCES jam_tracks(id);
|
||||
|
||||
CREATE VIEW payment_histories AS
|
||||
SELECT id AS sale_id,
|
||||
CAST(NULL as VARCHAR) AS recurly_transaction_web_hook_id,
|
||||
user_id,
|
||||
created_at,
|
||||
'sale' AS transaction_type
|
||||
FROM sales s
|
||||
UNION ALL
|
||||
SELECT CAST(NULL as VARCHAR) AS sale_id,
|
||||
id AS recurly_transaction_web_hook_id,
|
||||
user_id,
|
||||
transaction_at AS created_at,
|
||||
transaction_type
|
||||
FROM recurly_transaction_web_hooks;
|
||||
|
|
@ -1,4 +1,11 @@
|
|||
ALTER TABLE jam_track_tracks ADD COLUMN preview_mp3_url VARCHAR;
|
||||
ALTER TABLE jam_track_tracks ADD COLUMN preview_mp3_md5 VARCHAR;
|
||||
ALTER TABLE jam_track_tracks ADD COLUMN preview_mp3_length BIGINT;
|
||||
UPDATE jam_track_tracks SET preview_url = NULL where track_type = 'Master';
|
||||
DO $$
|
||||
BEGIN
|
||||
BEGIN
|
||||
ALTER TABLE jam_track_tracks ADD COLUMN preview_mp3_url VARCHAR;
|
||||
ALTER TABLE jam_track_tracks ADD COLUMN preview_mp3_md5 VARCHAR;
|
||||
ALTER TABLE jam_track_tracks ADD COLUMN preview_mp3_length BIGINT;
|
||||
EXCEPTION
|
||||
WHEN duplicate_column THEN RAISE NOTICE 'preview mp3 columns already exist in jam_tracks';
|
||||
END;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
ALTER TABLE sale_line_items ADD COLUMN recurly_adjustment_uuid VARCHAR(500);
|
||||
ALTER TABLE sale_line_items ADD COLUMN recurly_adjustment_credit_uuid VARCHAR(500);
|
||||
ALTER TABLE jam_track_rights ADD COLUMN recurly_adjustment_uuid VARCHAR(500);
|
||||
ALTER TABLE jam_track_rights ADD COLUMN recurly_adjustment_credit_uuid VARCHAR(500);
|
||||
ALTER TABLE sales ADD COLUMN recurly_invoice_id VARCHAR(500) UNIQUE;
|
||||
ALTER TABLE sales ADD COLUMN recurly_invoice_number INTEGER;
|
||||
|
||||
ALTER TABLE sales ADD COLUMN recurly_subtotal_in_cents INTEGER;
|
||||
ALTER TABLE sales ADD COLUMN recurly_tax_in_cents INTEGER;
|
||||
ALTER TABLE sales ADD COLUMN recurly_total_in_cents INTEGER;
|
||||
ALTER TABLE sales ADD COLUMN recurly_currency VARCHAR;
|
||||
|
||||
ALTER TABLE sale_line_items ADD COLUMN recurly_tax_in_cents INTEGER;
|
||||
ALTER TABLE sale_line_items ADD COLUMN recurly_total_in_cents INTEGER;
|
||||
ALTER TABLE sale_line_items ADD COLUMN recurly_currency VARCHAR;
|
||||
ALTER TABLE sale_line_items ADD COLUMN recurly_discount_in_cents INTEGER;
|
||||
|
||||
ALTER TABLE sales ADD COLUMN sale_type VARCHAR NOT NULL DEFAULT 'jamtrack';
|
||||
|
||||
ALTER TABLE recurly_transaction_web_hooks ALTER COLUMN subscription_id DROP NOT NULL;
|
||||
|
||||
CREATE INDEX recurly_transaction_web_hooks_invoice_id_ndx ON recurly_transaction_web_hooks(invoice_id);
|
||||
|
||||
ALTER TABLE jam_track_rights DROP COLUMN recurly_subscription_uuid;
|
||||
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE users ADD COLUMN show_whats_next_count INTEGER NOT NULL DEFAULT 0;
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE jam_track_rights ADD COLUMN signing_44 BOOLEAN DEFAULT FALSE;
|
||||
ALTER TABLE jam_track_rights ADD COLUMN signing_48 BOOLEAN DEFAULT FALSE;
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
CREATE TABLE signup_hints (
|
||||
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
anonymous_user_id VARCHAR(64) UNIQUE,
|
||||
redirect_location VARCHAR,
|
||||
want_jamblaster BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
user_id VARCHAR(64) REFERENCES users(id) ON DELETE CASCADE,
|
||||
expires_at TIMESTAMP,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
ALTER TABLE users ADD COLUMN want_jamblaster BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
|
|
@ -20,11 +20,12 @@ require 'resque_mailer'
|
|||
require 'rest-client'
|
||||
require 'zip'
|
||||
require 'csv'
|
||||
require 'tzinfo'
|
||||
|
||||
require "jam_ruby/constants/limits"
|
||||
require "jam_ruby/constants/notification_types"
|
||||
require "jam_ruby/constants/validation_messages"
|
||||
require "jam_ruby/errors/permission_error"
|
||||
require "jam_ruby/errors/jam_permission_error"
|
||||
require "jam_ruby/errors/state_error"
|
||||
require "jam_ruby/errors/jam_argument_error"
|
||||
require "jam_ruby/errors/conflict_error"
|
||||
|
|
@ -58,10 +59,12 @@ require "jam_ruby/resque/scheduled/score_history_sweeper"
|
|||
require "jam_ruby/resque/scheduled/scheduled_music_session_cleaner"
|
||||
require "jam_ruby/resque/scheduled/recordings_cleaner"
|
||||
require "jam_ruby/resque/scheduled/jam_tracks_cleaner"
|
||||
require "jam_ruby/resque/jam_tracks_builder"
|
||||
require "jam_ruby/resque/scheduled/stats_maker"
|
||||
require "jam_ruby/resque/scheduled/tally_affiliates"
|
||||
require "jam_ruby/resque/jam_tracks_builder"
|
||||
require "jam_ruby/resque/google_analytics_event"
|
||||
require "jam_ruby/resque/batch_email_job"
|
||||
require "jam_ruby/resque/long_running"
|
||||
require "jam_ruby/mq_router"
|
||||
require "jam_ruby/recurly_client"
|
||||
require "jam_ruby/base_manager"
|
||||
|
|
@ -69,6 +72,7 @@ require "jam_ruby/connection_manager"
|
|||
require "jam_ruby/version"
|
||||
require "jam_ruby/environment"
|
||||
require "jam_ruby/init"
|
||||
require "jam_ruby/app/mailers/admin_mailer"
|
||||
require "jam_ruby/app/mailers/user_mailer"
|
||||
require "jam_ruby/app/mailers/invited_user_mailer"
|
||||
require "jam_ruby/app/mailers/corp_mailer"
|
||||
|
|
@ -99,6 +103,11 @@ require "jam_ruby/models/genre_player"
|
|||
require "jam_ruby/models/genre"
|
||||
require "jam_ruby/models/user"
|
||||
require "jam_ruby/models/anonymous_user"
|
||||
require "jam_ruby/models/signup_hint"
|
||||
require "jam_ruby/models/machine_fingerprint"
|
||||
require "jam_ruby/models/machine_extra"
|
||||
require "jam_ruby/models/fraud_alert"
|
||||
require "jam_ruby/models/fingerprint_whitelist"
|
||||
require "jam_ruby/models/rsvp_request"
|
||||
require "jam_ruby/models/rsvp_slot"
|
||||
require "jam_ruby/models/rsvp_request_rsvp_slot"
|
||||
|
|
@ -198,12 +207,19 @@ require "jam_ruby/app/mailers/async_mailer"
|
|||
require "jam_ruby/app/mailers/batch_mailer"
|
||||
require "jam_ruby/app/mailers/progress_mailer"
|
||||
require "jam_ruby/models/affiliate_partner"
|
||||
require "jam_ruby/models/affiliate_legalese"
|
||||
require "jam_ruby/models/affiliate_quarterly_payment"
|
||||
require "jam_ruby/models/affiliate_monthly_payment"
|
||||
require "jam_ruby/models/affiliate_traffic_total"
|
||||
require "jam_ruby/models/affiliate_referral_visit"
|
||||
require "jam_ruby/models/affiliate_payment"
|
||||
require "jam_ruby/models/chat_message"
|
||||
require "jam_ruby/models/shopping_cart"
|
||||
require "jam_ruby/models/generic_state"
|
||||
require "jam_ruby/models/score_history"
|
||||
require "jam_ruby/models/jam_company"
|
||||
require "jam_ruby/models/user_sync"
|
||||
require "jam_ruby/models/payment_history"
|
||||
require "jam_ruby/models/video_source"
|
||||
require "jam_ruby/models/text_message"
|
||||
require "jam_ruby/models/sale"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
module JamRuby
|
||||
# sends out a boring ale
|
||||
class AdminMailer < ActionMailer::Base
|
||||
include SendGrid
|
||||
|
||||
|
||||
DEFAULT_SENDER = "JamKazam <noreply@jamkazam.com>"
|
||||
|
||||
default :from => DEFAULT_SENDER
|
||||
|
||||
sendgrid_category :use_subject_lines
|
||||
#sendgrid_enable :opentrack, :clicktrack # this makes our emails creepy, imo (seth)
|
||||
sendgrid_unique_args :env => Environment.mode
|
||||
|
||||
def alerts(options)
|
||||
mail(to: APP_CONFIG.email_alerts_alias,
|
||||
from: APP_CONFIG.email_generic_from,
|
||||
body: options[:body],
|
||||
content_type: "text/plain",
|
||||
subject: options[:subject])
|
||||
end
|
||||
|
||||
def recurly_alerts(user, options)
|
||||
|
||||
body = options[:body]
|
||||
body << "\n\n"
|
||||
body << "User " << user.admin_url + "\n"
|
||||
body << "User's JamTracks " << user.jam_track_rights_admin_url + "\n"
|
||||
|
||||
mail(to: APP_CONFIG.email_recurly_notice,
|
||||
from: APP_CONFIG.email_generic_from,
|
||||
body: body,
|
||||
content_type: "text/plain",
|
||||
subject: options[:subject])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -52,7 +52,7 @@ module JamRuby
|
|||
|
||||
def generate_signup_url(invited_user)
|
||||
invited_user.generate_signup_url
|
||||
# "http://www.jamkazam.com/signup?invitation_code=#{invited_user.invitation_code}"
|
||||
# "https://www.jamkazam.com/signup?invitation_code=#{invited_user.invitation_code}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -23,6 +23,6 @@ class JamTrackRightUploader < CarrierWave::Uploader::Base
|
|||
end
|
||||
|
||||
def filename
|
||||
"#{model.store_dir}/#{model.filename}" if model.id
|
||||
"#{model.store_dir}/#{model.filename(mounted_as)}" if model.id
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
<br/>
|
||||
<br/>
|
||||
<p>
|
||||
This email was received because someone left feedback at <a style="color: #ffcc00;" href="http://www.jamkazam.com/corp/contact">http://www.jamkazam.com/corp/contact</a>
|
||||
This email was received because someone left feedback at <a style="color: #ffcc00;" href="https://www.jamkazam.com/corp/contact">http://www.jamkazam.com/corp/contact</a>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -5,4 +5,4 @@ From <%= @email %>:
|
|||
<%= @body %>
|
||||
|
||||
|
||||
This email was received because someone left feedback at http://www.jamkazam.com/corp/contact
|
||||
This email was received because someone left feedback at https://www.jamkazam.com/corp/contact
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
</p>
|
||||
|
||||
<p>
|
||||
<a style="color: #ffcc00;" href="http://www.jamkazam.com/downloads">Go to Download Page</a>
|
||||
<a style="color: #ffcc00;" href="https://www.jamkazam.com/downloads">Go to Download Page</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Hello <%= EmailBatchProgression::VAR_FIRST_NAME %> --
|
|||
|
||||
We noticed that you have registered as a JamKazam musician, but you have not yet downloaded and started using the free JamKazam application. You can find other musicians and listen to sessions and recordings on our website, but you need the free JamKazam application to play with other musicians online. Please click the link below to go to the download page for the free JamKazam application, or visit our JamKazam support center so that we can help you get up and running.
|
||||
|
||||
Go to Download Page: http://www.jamkazam.com/downloads
|
||||
Go to Download Page: https://www.jamkazam.com/downloads
|
||||
|
||||
Go to Support Center: https://jamkazam.desk.com
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
It’s still very early in our company’s development, so we don’t have zillions of users online on our service yet. If you click Find Session, you will often not find a good session to join, both due to the number of musicians online at any given time, and also because you won’t see private sessions where groups of musicians don’t want to be interrupted in their sessions.
|
||||
</p>
|
||||
|
||||
<p>If you are having trouble getting into sessions, we’d suggest you click the Musicians tile on the home screen of the app or the website: <a style="color: #ffcc00;" href="http://www.jamkazam.com/client#/musicians">Go To Musicians Page</a>
|
||||
<p>If you are having trouble getting into sessions, we’d suggest you click the Musicians tile on the home screen of the app or the website: <a style="color: #ffcc00;" href="https://www.jamkazam.com/client#/musicians">Go To Musicians Page</a>
|
||||
</p>
|
||||
|
||||
<p>This will display the JamKazam musicians sorted by latency to you - in other words, you can see which musicians have good network connections to you. Any musicians with green and yellow latency scores have good enough connections to support a play session with you. We recommend that read the profiles of these musicians to find others with shared musical interests and good network connections to you, and then use the Message button to say hi and see if they are interested in playing with you. If they are, use the Connect button to “friend” them on JamKazam, and use the Message button to set up a time to meet online for a session.
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ We noticed that you haven’t yet played in a JamKazam session with multiple mus
|
|||
Find Other Musicians on JamKazam
|
||||
It’s still very early in our company’s development, so we don’t have zillions of users online on our service yet. If you click Find Session, you will often not find a good session to join, both due to the number of musicians online at any given time, and also because you won’t see private sessions where groups of musicians don’t want to be interrupted in their sessions.
|
||||
|
||||
If you are having trouble getting into sessions, we’d suggest you click the Musicians tile on the home screen of the app or the website: http://www.jamkazam.com/client#/musicians
|
||||
If you are having trouble getting into sessions, we’d suggest you click the Musicians tile on the home screen of the app or the website: https://www.jamkazam.com/client#/musicians
|
||||
|
||||
This will display the JamKazam musicians sorted by latency to you - in other words, you can see which musicians have good network connections to you. Any musicians with green and yellow latency scores have good enough connections to support a play session with you. We recommend that read the profiles of these musicians to find others with shared musical interests and good network connections to you, and then use the Message button to say hi and see if they are interested in playing with you. If they are, use the Connect button to “friend” them on JamKazam, and use the Message button to set up a time to meet online for a session.
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
</p>
|
||||
|
||||
<p>Find Other Musicians on JamKazam<br />
|
||||
To find and connect with other musicians who are already on JamKazam, we’d suggest you click the Musicians tile on the home screen of the app or the website: <a style="color: #ffcc00;" href="http://www.jamkazam.com/client#/musicians">Go To Musicians Page</a>
|
||||
To find and connect with other musicians who are already on JamKazam, we’d suggest you click the Musicians tile on the home screen of the app or the website: <a style="color: #ffcc00;" href="https://www.jamkazam.com/client#/musicians">Go To Musicians Page</a>
|
||||
</p>
|
||||
|
||||
<p>This will display the JamKazam musicians sorted by latency to you - in other words, you can see which musicians have good network connections to you. Any musicians with green and yellow latency scores have good enough connections to support a play session with you. We recommend that you read the profiles of these musicians to find others with shared musical interests and good network connections to you, and then use the Message button to say hi and see if they are interested in playing with you. If they are, use the Connect button to “friend” them on JamKazam, and use the Message button to set up a time to meet online for a session.
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ Hello <%= EmailBatchProgression::VAR_FIRST_NAME %> --
|
|||
We noticed that you haven’t yet connected with any friends on JamKazam. Connecting with friends is the best way to help you get into sessions with other musicians on JamKazam. Here are a couple of good ways to connect with others.
|
||||
|
||||
Find Other Musicians on JamKazam
|
||||
To find and connect with other musicians who are already on JamKazam, we’d suggest you click the Musicians tile on the home screen of the app or the website: http://www.jamkazam.com/client#/musicians
|
||||
To find and connect with other musicians who are already on JamKazam, we’d suggest you click the Musicians tile on the home screen of the app or the website: https://www.jamkazam.com/client#/musicians
|
||||
|
||||
This will display the JamKazam musicians sorted by latency to you - in other words, you can see which musicians have good network connections to you. Any musicians with green and yellow latency scores have good enough connections to support a play session with you. We recommend that you read the profiles of these musicians to find others with shared musical interests and good network connections to you, and then use the Message button to say hi and see if they are interested in playing with you. If they are, use the Connect button to “friend” them on JamKazam, and use the Message button to set up a time to meet online for a session.
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
</p>
|
||||
|
||||
<% [:twitter, :facebook, :google].each do |site| %>
|
||||
<%= link_to(image_tag("http://www.jamkazam.com/assets/content/icon_#{site}.png", :style => "vertical-align:top"), "http://www.jamkazam.com/endorse/@USERID/#{site}?src=email") %>
|
||||
<%= link_to(image_tag("https://www.jamkazam.com/assets/content/icon_#{site}.png", :style => "vertical-align:top"), "https://www.jamkazam.com/endorse/@USERID/#{site}?src=email") %>
|
||||
<% end %>
|
||||
|
||||
<p>-- Team JamKazam
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ Hello <%= EmailBatchProgression::VAR_FIRST_NAME %> --
|
|||
JamKazam is a young company/service built through the sweat and commitment of a small group of music-loving techies. Please help us continue to grow the service and attract more musicians to play online by liking and/or following us on Facebook, Twitter, and Google+. Just click the icons below to give us little push, thanks!
|
||||
|
||||
<% [:twitter, :facebook, :google].each do |site| %>
|
||||
http://www.jamkazam.com/endorse/@USERID/#{site}?src=email
|
||||
https://www.jamkazam.com/endorse/@USERID/#{site}?src=email
|
||||
|
||||
<% end %>
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ Hi <%= @user.first_name %>,
|
|||
<% end %>
|
||||
</table>
|
||||
</p>
|
||||
<p>There are currently <%= @new_musicians.size%> musicians on JamKazam with low enough latency Internet connections to you to support a good online session. To see ALL the JamKazam musicians with whom you may want to connect and play, view our Musicians page at: <a style="color: #ffcc00;" href="http://www.jamkazam.com/client#/musicians">http://www.jamkazam.com/client#/musicians</a>.
|
||||
<p>There are currently <%= @new_musicians.size%> musicians on JamKazam with low enough latency Internet connections to you to support a good online session. To see ALL the JamKazam musicians with whom you may want to connect and play, view our Musicians page at: <a style="color: #ffcc00;" href="https://www.jamkazam.com/client#/musicians">http://www.jamkazam.com/client#/musicians</a>.
|
||||
</p>
|
||||
|
||||
<p>Best Regards,</p>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ The following new musicians have joined JamKazam within the last week, and have
|
|||
<%= user.biography %>
|
||||
<% end %>
|
||||
|
||||
There are currently <%= @new_musicians.size%> musicians on JamKazam with low enough latency Internet connections to you to support a good online session. To see ALL the JamKazam musicians with whom you may want to connect and play, view our Musicians page at: http://www.jamkazam.com/client#/musicians.
|
||||
There are currently <%= @new_musicians.size%> musicians on JamKazam with low enough latency Internet connections to you to support a good online session. To see ALL the JamKazam musicians with whom you may want to connect and play, view our Musicians page at: https://www.jamkazam.com/client#/musicians.
|
||||
|
||||
Best Regards,
|
||||
Team JamKazam
|
||||
|
|
|
|||
|
|
@ -68,16 +68,16 @@
|
|||
<td><%= sess.genre.description %></td>
|
||||
<td>
|
||||
<%= sess.name %><br/>
|
||||
<a style="color: #ffcc00;" href="<%= "http://www.jamkazam.com/sessions/#{sess.id}/details" %>">Details</a>
|
||||
<a style="color: #ffcc00;" href="<%= "https://www.jamkazam.com/sessions/#{sess.id}/details" %>">Details</a>
|
||||
</td>
|
||||
<td><%= sess.description %></td>
|
||||
<td style="text-align:center">
|
||||
<span class="latency">
|
||||
<span class="latency-value"><%= (sess.latency / 2).round %> ms</span>
|
||||
<% if sess.latency <= (APP_CONFIG.max_good_full_score / 2) %>
|
||||
<%= image_tag("http://www.jamkazam.com/assets/content/icon_green_score.png", alt: 'good score icon') %>
|
||||
<%= image_tag("https://www.jamkazam.com/assets/content/icon_green_score.png", alt: 'good score icon') %>
|
||||
<% else %>
|
||||
<%= image_tag("http://www.jamkazam.com/assets/content/icon_yellow_score.png", alt: 'fair score icon') %>
|
||||
<%= image_tag("https://www.jamkazam.com/assets/content/icon_yellow_score.png", alt: 'fair score icon') %>
|
||||
<% end %>
|
||||
</span>
|
||||
</td>
|
||||
|
|
@ -86,7 +86,7 @@
|
|||
</tbody>
|
||||
</table>
|
||||
|
||||
<p>To see ALL the scheduled sessions that you might be interested in joining, view our <a style="color: #ffcc00;" href="http://www.jamkazam.com/client#/findSession">Find Session page</a>.</p>
|
||||
<p>To see ALL the scheduled sessions that you might be interested in joining, view our <a style="color: #ffcc00;" href="https://www.jamkazam.com/client#/findSession">Find Session page</a>.</p>
|
||||
|
||||
<p>Best Regards,</p>
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ GENRE | NAME | DESCRIPTION | LATENCY
|
|||
<%= sess.genre.description %> | <%= sess.name %> | <%= sess.description %> | <%= sess.latency %> ms
|
||||
<% end %>
|
||||
|
||||
To see ALL the scheduled sessions that you might be interested in joining, view our Find Session page at: http://www.jamkazam.com/client#/findSession.
|
||||
To see ALL the scheduled sessions that you might be interested in joining, view our Find Session page at: https://www.jamkazam.com/client#/findSession.
|
||||
|
||||
Best Regards,
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
<body bgcolor="#000000" style="margin-top:10px;font-family:Arial, Helvetica, sans-serif;">
|
||||
<table bgcolor="#262626" width="650" align="center" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td><img src="http://www.jamkazam.com/assets/email/header.png" width="650" height="183" alt="JamKazam"></td>
|
||||
<td><img src="https://www.jamkazam.com/assets/email/header.png" width="650" height="183" alt="JamKazam"></td>
|
||||
</tr>
|
||||
</table>
|
||||
<table bgcolor="#262626" width="650" align="center" cellpadding="30" cellspacing="0">
|
||||
|
|
@ -51,7 +51,7 @@
|
|||
|
||||
<!-- CALL OUT BOX -->
|
||||
</font></p>
|
||||
<p style="margin-top:0px"><font size="2" color="#7FACBA" face="Arial, Helvetica, sans-serif">This email was sent to you because you have an account at <a style="color: #ffcc00;" href="http://www.jamkazam.com">JamKazam</a>.
|
||||
<p style="margin-top:0px"><font size="2" color="#7FACBA" face="Arial, Helvetica, sans-serif">This email was sent to you because you have an account at <a style="color: #ffcc00;" href="https://www.jamkazam.com">JamKazam</a>.
|
||||
</td></tr></table>
|
||||
|
||||
</td>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
|
||||
<% unless @suppress_user_has_account_footer == true %>
|
||||
This email was sent to you because you have an account at JamKazam / http://www.jamkazam.com.
|
||||
This email was sent to you because you have an account at JamKazam / https://www.jamkazam.com.
|
||||
<% end %>
|
||||
|
||||
Copyright <%= Time.now.year %> JamKazam, Inc. All rights reserved.
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
<td align="left">
|
||||
|
||||
<!-- CALL OUT BOX -->
|
||||
<p style="margin-top:0px"><font size="2" color="#7FACBA" face="Arial, Helvetica, sans-serif">This email was sent to you because you have an account at <a style="color: #ffcc00;" href="http://www.jamkazam.com">JamKazam</a>. Click <a style="color: #ffcc00;" href="http://www.jamkazam.com/client#/account/profile">here to unsubscribe</a> and update your profile settings.
|
||||
<p style="margin-top:0px"><font size="2" color="#7FACBA" face="Arial, Helvetica, sans-serif">This email was sent to you because you have an account at <a style="color: #ffcc00;" href="http://www.jamkazam.com">JamKazam</a>. Click <a style="color: #ffcc00;" href="http://www.jamkazam.com/unsubscribe/#{@user.unsubscribe_token}">here to unsubscribe</a> and update your profile settings.
|
||||
</font></p>
|
||||
</td></tr></table>
|
||||
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@
|
|||
<%= yield %>
|
||||
<% end %>
|
||||
|
||||
<% unless @suppress_user_has_account_footer == true %>
|
||||
This email was sent to you because you have an account at JamKazam / http://www.jamkazam.com. Visit your profile page to unsubscribe: http://www.jamkazam.com/client#/account/profile.
|
||||
<% unless @user.nil? || @suppress_user_has_account_footer == true %>
|
||||
This email was sent to you because you have an account at JamKazam / https://www.jamkazam.com. To unsubscribe: https://www.jamkazam.com/unsubscribe/<%=@user.unsubscribe_token%>.
|
||||
<% end %>
|
||||
|
||||
Copyright <%= Time.now.year %> JamKazam, Inc. All rights reserved.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
module JamRuby
|
||||
class JamPermissionError < Exception
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
module JamRuby
|
||||
class PermissionError < Exception
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -91,13 +91,15 @@ module JamRuby
|
|||
true
|
||||
end
|
||||
|
||||
def synchronize_metadata(jam_track, metadata, metalocation, original_artist, name)
|
||||
def synchronize_metadata(jam_track, metadata, metalocation, original_artist, name, options)
|
||||
|
||||
metadata ||= {}
|
||||
self.name = metadata["name"] || name
|
||||
|
||||
if jam_track.new_record?
|
||||
jam_track.id = "#{JamTrack.count + 1}" # default is UUID, but the initial import was based on auto-increment ID, so we'll maintain that
|
||||
latest_jamtrack = JamTrack.order('created_at desc').first
|
||||
id = latest_jamtrack.nil? ? 1 : latest_jamtrack.id.to_i + 1
|
||||
jam_track.id = "#{id}" # default is UUID, but the initial import was based on auto-increment ID, so we'll maintain that
|
||||
jam_track.status = 'Staging'
|
||||
jam_track.metalocation = metalocation
|
||||
jam_track.original_artist = metadata["original_artist"] || original_artist
|
||||
|
|
@ -107,13 +109,20 @@ module JamRuby
|
|||
jam_track.price = 1.99
|
||||
jam_track.reproduction_royalty_amount = 0
|
||||
jam_track.licensor_royalty_amount = 0
|
||||
jam_track.sales_region = 'United States'
|
||||
jam_track.sales_region = 'Worldwide'
|
||||
jam_track.recording_type = 'Cover'
|
||||
jam_track.description = "This is a JamTrack audio file for use exclusively with the JamKazam service. This JamTrack is a high quality cover of the #{jam_track.original_artist} song \"#{jam_track.name}\"."
|
||||
else
|
||||
#@@log.debug("#{self.name} skipped because it already exists in database")
|
||||
finish("jam_track_exists", "")
|
||||
return false
|
||||
if !options[:resync_audio]
|
||||
#@@log.debug("#{self.name} skipped because it already exists in database")
|
||||
finish("jam_track_exists", "")
|
||||
return false
|
||||
else
|
||||
# jamtrack exists, leave it be
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
saved = jam_track.save
|
||||
|
|
@ -318,24 +327,31 @@ module JamRuby
|
|||
|
||||
def set_custom_weight(track)
|
||||
weight = 5
|
||||
case track.instrument_id
|
||||
when 'electric guitar'
|
||||
weight = 1
|
||||
when 'acoustic guitar'
|
||||
weight = 2
|
||||
when 'drums'
|
||||
weight = 3
|
||||
when 'keys'
|
||||
weight = 4
|
||||
when 'computer'
|
||||
weight = 10
|
||||
else
|
||||
weight = 5
|
||||
end
|
||||
if track.track_type == 'Master'
|
||||
weight = 1000
|
||||
# if there are any persisted tracks, do not sort from scratch; just stick new stuff at the end
|
||||
|
||||
if track.persisted?
|
||||
weight = track.position
|
||||
else
|
||||
case track.instrument_id
|
||||
when 'electric guitar'
|
||||
weight = 100
|
||||
when 'acoustic guitar'
|
||||
weight = 200
|
||||
when 'drums'
|
||||
weight = 300
|
||||
when 'keys'
|
||||
weight = 400
|
||||
when 'computer'
|
||||
weight = 600
|
||||
else
|
||||
weight = 500
|
||||
end
|
||||
if track.track_type == 'Master'
|
||||
weight = 1000
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
weight
|
||||
end
|
||||
|
||||
|
|
@ -346,10 +362,19 @@ module JamRuby
|
|||
a_weight <=> b_weight
|
||||
end
|
||||
|
||||
# default to 1, but if there are any persisted tracks, this will get manipulated to be +1 the highest persisted track
|
||||
position = 1
|
||||
sorted_tracks.each do |track|
|
||||
track.position = position
|
||||
position = position + 1
|
||||
if track.persisted?
|
||||
# persisted tracks should be sorted at the beginning of the sorted_tracks,
|
||||
# so this just keeps moving the 'position builder' up to +1 of the last persisted track
|
||||
position = track.position + 1
|
||||
else
|
||||
track.position = position
|
||||
position = position + 1
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
sorted_tracks[sorted_tracks.length - 1].position = 1000
|
||||
|
|
@ -359,11 +384,40 @@ module JamRuby
|
|||
|
||||
def synchronize_audio(jam_track, metadata, s3_path, skip_audio_upload)
|
||||
|
||||
attempt_to_match_existing_tracks = true
|
||||
|
||||
# find all wav files in the JamTracks s3 bucket
|
||||
wav_files = fetch_wav_files(s3_path)
|
||||
|
||||
tracks = []
|
||||
|
||||
wav_files.each do |wav_file|
|
||||
|
||||
if attempt_to_match_existing_tracks
|
||||
# try to find a matching track from the JamTrack based on the name of the 44.1 path
|
||||
basename = File.basename(wav_file)
|
||||
ogg_44100_filename = File.basename(basename, ".wav") + "-44100.ogg"
|
||||
|
||||
found_track = nil
|
||||
jam_track.jam_track_tracks.each do |jam_track_track|
|
||||
|
||||
if jam_track_track["url_44"] && jam_track_track["url_44"].end_with?(ogg_44100_filename)
|
||||
# found a match!
|
||||
found_track = jam_track_track
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if found_track
|
||||
@@log.debug("found a existing track to reuse")
|
||||
found_track.original_audio_s3_path = wav_file
|
||||
tracks << found_track
|
||||
next
|
||||
end
|
||||
end
|
||||
|
||||
@@log.debug("no existing track found; creating a new one")
|
||||
|
||||
track = JamTrackTrack.new
|
||||
track.original_audio_s3_path = wav_file
|
||||
|
||||
|
|
@ -388,6 +442,15 @@ module JamRuby
|
|||
tracks << track
|
||||
end
|
||||
|
||||
jam_track.jam_track_tracks.each do |jam_track_track|
|
||||
# delete all jam_track_tracks not in the tracks array
|
||||
unless tracks.include?(jam_track_track)
|
||||
@@log.info("destroying removed JamTrackTrack #{jam_track_track.inspect}")
|
||||
jam_track_track.destroy # should also delete s3 files associated with this jamtrack
|
||||
end
|
||||
end
|
||||
|
||||
@@log.info("sorting tracks")
|
||||
tracks = sort_tracks(tracks)
|
||||
|
||||
jam_track.jam_track_tracks = tracks
|
||||
|
|
@ -481,15 +544,16 @@ module JamRuby
|
|||
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
|
||||
preview_succeeded = synchronize_master_preview(track, tmp_dir, ogg_44100, ogg_44100_digest) if track.track_type == 'Master'
|
||||
if track.track_type == 'Master'
|
||||
preview_succeeded = synchronize_master_preview(track, tmp_dir, ogg_44100, ogg_44100_digest)
|
||||
|
||||
if !preview_succeeded
|
||||
return false
|
||||
if !preview_succeeded
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
track.save!
|
||||
|
|
@ -596,7 +660,7 @@ module JamRuby
|
|||
original_artist = parsed_metalocation[1]
|
||||
name = parsed_metalocation[2]
|
||||
|
||||
success = synchronize_metadata(jam_track, metadata, metalocation, original_artist, name)
|
||||
success = synchronize_metadata(jam_track, metadata, metalocation, original_artist, name, options)
|
||||
|
||||
return unless success
|
||||
|
||||
|
|
@ -614,7 +678,8 @@ module JamRuby
|
|||
def synchronize_recurly(jam_track)
|
||||
begin
|
||||
recurly = RecurlyClient.new
|
||||
recurly.create_jam_track_plan(jam_track) unless recurly.find_jam_track_plan(jam_track)
|
||||
# no longer create JamTrack plans: VRFS-3028
|
||||
# recurly.create_jam_track_plan(jam_track) unless recurly.find_jam_track_plan(jam_track)
|
||||
rescue RecurlyClientError => x
|
||||
finish('recurly_create_plan', x.errors.to_s)
|
||||
return false
|
||||
|
|
@ -654,6 +719,33 @@ module JamRuby
|
|||
|
||||
end
|
||||
|
||||
def synchronize_preview(jam_track)
|
||||
importer = JamTrackImporter.new
|
||||
importer.name = jam_track.name
|
||||
|
||||
error_occurred = false
|
||||
error_msg = nil
|
||||
jam_track.jam_track_tracks.each do |track|
|
||||
next if track.track_type == 'Master'
|
||||
|
||||
if track.preview_start_time
|
||||
track.generate_preview
|
||||
if track.preview_generate_error
|
||||
error_occurred = true
|
||||
error_msg = track.preview_generate_error
|
||||
else
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if error_occurred
|
||||
importer.finish('preview_error', error_msg)
|
||||
else
|
||||
importer.finish('success', nil)
|
||||
end
|
||||
importer
|
||||
end
|
||||
|
||||
def synchronize_jamtrack_master_preview(jam_track)
|
||||
importer = JamTrackImporter.new
|
||||
importer.name = jam_track.name
|
||||
|
|
@ -676,6 +768,32 @@ module JamRuby
|
|||
importer
|
||||
end
|
||||
|
||||
|
||||
def synchronize_previews
|
||||
importers = []
|
||||
|
||||
JamTrack.all.each do |jam_track|
|
||||
importers << synchronize_preview(jam_track)
|
||||
end
|
||||
|
||||
@@log.info("SUMMARY")
|
||||
@@log.info("-------")
|
||||
importers.each do |importer|
|
||||
if importer
|
||||
if importer.reason == "success" || importer.reason == "no_preview_start_time"
|
||||
@@log.info("#{importer.name} #{importer.reason}")
|
||||
else
|
||||
@@log.error("#{importer.name} failed to import.")
|
||||
@@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_jamtrack_master_previews
|
||||
importers = []
|
||||
|
||||
|
|
|
|||
|
|
@ -22,9 +22,21 @@ module JamRuby
|
|||
save_jam_track_right_jkz(jam_track_right, sample_rate)
|
||||
end
|
||||
|
||||
# increment the step, which causes a notification to be sent to the client so it can keep the UI fresh as the packaging step goes on
|
||||
def bump_step(jam_track_right, step)
|
||||
last_step_at = Time.now
|
||||
jam_track_right.current_packaging_step = step
|
||||
jam_track_right.last_step_at = Time.now
|
||||
JamTrackRight.where(:id => jam_track_right.id).update_all(last_step_at: last_step_at, current_packaging_step: step)
|
||||
SubscriptionMessage.jam_track_signing_job_change(jam_track_right)
|
||||
step = step + 1
|
||||
step
|
||||
end
|
||||
|
||||
def save_jam_track_right_jkz(jam_track_right, sample_rate=48)
|
||||
jam_track = jam_track_right.jam_track
|
||||
py_root = APP_CONFIG.jamtracks_dir
|
||||
step = 0
|
||||
Dir.mktmpdir do |tmp_dir|
|
||||
jam_file_opts=""
|
||||
jam_track.jam_track_tracks.each do |jam_track_track|
|
||||
|
|
@ -36,6 +48,9 @@ module JamRuby
|
|||
track_filename = File.join(tmp_dir, nm)
|
||||
track_url = jam_track_track.sign_url(120, sample_rate)
|
||||
@@log.info("downloading #{track_url} to #{track_filename}")
|
||||
|
||||
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}")}"
|
||||
end
|
||||
|
|
@ -48,6 +63,8 @@ module JamRuby
|
|||
version = jam_track.version
|
||||
@@log.info "Executing python source in #{py_file}, outputting to #{tmp_dir} (#{output_jkz})"
|
||||
|
||||
step = bump_step(jam_track_right, step)
|
||||
|
||||
# From http://stackoverflow.com/questions/690151/getting-output-of-system-calls-in-ruby/5970819#5970819:
|
||||
cli = "python #{py_file} -D -k #{sku} -p #{Shellwords.escape(tmp_dir)}/pkey.pem -s #{Shellwords.escape(tmp_dir)}/skey.pem #{jam_file_opts} -o #{Shellwords.escape(output_jkz)} -t #{Shellwords.escape(title)} -V #{Shellwords.escape(version)}"
|
||||
Open3.popen3(cli) do |stdin, stdout, stderr, wait_thr|
|
||||
|
|
@ -65,10 +82,18 @@ module JamRuby
|
|||
else
|
||||
jam_track_right.url_44.store!(File.open(output_jkz, "rb"))
|
||||
end
|
||||
|
||||
jam_track_right.signed=true
|
||||
jam_track_right.downloaded_since_sign=false
|
||||
jam_track_right.private_key=File.read("#{tmp_dir}/skey.pem")
|
||||
|
||||
if sample_rate == 48
|
||||
jam_track_right.signed_48 = true
|
||||
jam_track_right.private_key_48 = File.read("#{tmp_dir}/skey.pem")
|
||||
else
|
||||
jam_track_right.signed_44 = true
|
||||
jam_track_right.private_key_44 = File.read("#{tmp_dir}/skey.pem")
|
||||
end
|
||||
|
||||
jam_track_right.signing_queued_at = nil # if left set, throws off signing_state on subsequent signing attempts
|
||||
jam_track_right.downloaded_since_sign = false
|
||||
|
||||
jam_track_right.save!
|
||||
end
|
||||
end # mktmpdir
|
||||
|
|
@ -78,7 +103,7 @@ module JamRuby
|
|||
def copy_url_to_file(url, filename)
|
||||
uri = URI(url)
|
||||
open(filename, 'w+b') do |io|
|
||||
Net::HTTP.start(uri.host, uri.port) do |http|
|
||||
Net::HTTP.start(uri.host, uri.port, use_ssl: url.start_with?('https') ? true : false) do |http|
|
||||
request = Net::HTTP::Get.new uri
|
||||
http.request request do |response|
|
||||
response_code = response.code.to_i
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ module JamRuby
|
|||
end
|
||||
|
||||
def self.jam_track_signing_job_change(jam_track_right)
|
||||
Notification.send_subscription_message('jam_track_right', jam_track_right.id.to_s, {signing_state: jam_track_right.signing_state}.to_json )
|
||||
Notification.send_subscription_message('jam_track_right', jam_track_right.id.to_s, {signing_state: jam_track_right.signing_state, current_packaging_step: jam_track_right.current_packaging_step, packaging_steps: jam_track_right.packaging_steps}.to_json )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ module JamRuby
|
|||
|
||||
self.table_name = 'active_music_sessions'
|
||||
|
||||
attr_accessor :legal_terms, :max_score, :opening_jam_track, :opening_recording, :opening_backing_track, :opening_metronome
|
||||
attr_accessor :legal_terms, :max_score, :opening_jam_track, :opening_recording, :opening_backing_track, :opening_metronome, :jam_track_id
|
||||
|
||||
belongs_to :claimed_recording, :class_name => "JamRuby::ClaimedRecording", :foreign_key => "claimed_recording_id", :inverse_of => :playing_sessions
|
||||
belongs_to :claimed_recording_initiator, :class_name => "JamRuby::User", :inverse_of => :playing_claimed_recordings, :foreign_key => "claimed_recording_initiator_id"
|
||||
|
|
@ -774,6 +774,7 @@ module JamRuby
|
|||
self.opening_jam_track = true
|
||||
self.save
|
||||
self.opening_jam_track = false
|
||||
#self.tick_track_changes
|
||||
end
|
||||
|
||||
def close_jam_track
|
||||
|
|
@ -823,5 +824,19 @@ module JamRuby
|
|||
music_session.band_id = session_history.band.id unless session_history.band.nil?
|
||||
session_history.save!
|
||||
end
|
||||
|
||||
def self.stats
|
||||
stats = {}
|
||||
|
||||
result = ActiveMusicSession.select('count(distinct(id)) AS total, count(distinct(jam_track_initiator_id)) as jam_track_count, count(distinct(backing_track_initiator_id)) as backing_track_count, count(distinct(metronome_initiator_id)) as metronome_count, count(distinct(claimed_recording_initiator_id)) as recording_count').first
|
||||
|
||||
stats['count'] = result['total'].to_i
|
||||
stats['jam_track_count'] = result['jam_track_count'].to_i
|
||||
stats['backing_track_count'] = result['backing_track_count'].to_i
|
||||
stats['metronome_count'] = result['metronome_count'].to_i
|
||||
stats['recording_count'] = result['recording_count'].to_i
|
||||
|
||||
stats
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
class JamRuby::AffiliateLegalese < ActiveRecord::Base
|
||||
self.table_name = 'affiliate_legalese'
|
||||
|
||||
has_many :affiliate_partners, :class_name => "JamRuby::AffiliatePartner", :foreign_key => :legalese_id
|
||||
|
||||
end
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
class JamRuby::AffiliateMonthlyPayment < ActiveRecord::Base
|
||||
|
||||
belongs_to :affiliate_partner, class_name: 'JamRuby::AffiliatePartner', inverse_of: :months
|
||||
|
||||
|
||||
def self.index(user, options)
|
||||
unless user.affiliate_partner
|
||||
return [[], nil]
|
||||
end
|
||||
|
||||
page = options[:page].to_i
|
||||
per_page = options[:per_page].to_i
|
||||
|
||||
if page == 0
|
||||
page = 1
|
||||
end
|
||||
|
||||
if per_page == 0
|
||||
per_page = 50
|
||||
end
|
||||
|
||||
start = (page -1 ) * per_page
|
||||
limit = per_page
|
||||
|
||||
query = AffiliateMonthlyPayment
|
||||
.paginate(page: page, per_page: per_page)
|
||||
.where(affiliate_partner_id: user.affiliate_partner.id)
|
||||
.order('year ASC, month ASC')
|
||||
|
||||
if query.length == 0
|
||||
[query, nil]
|
||||
elsif query.length < limit
|
||||
[query, nil]
|
||||
else
|
||||
[query, start + limit]
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -1,25 +1,75 @@
|
|||
class JamRuby::AffiliatePartner < ActiveRecord::Base
|
||||
belongs_to :partner_user, :class_name => "JamRuby::User", :foreign_key => :partner_user_id
|
||||
has_many :user_referrals, :class_name => "JamRuby::User", :foreign_key => :affiliate_referral_id
|
||||
self.table_name = 'affiliate_partners'
|
||||
|
||||
belongs_to :partner_user, :class_name => "JamRuby::User", :foreign_key => :partner_user_id, inverse_of: :affiliate_partner
|
||||
has_many :user_referrals, :class_name => "JamRuby::User", :foreign_key => :affiliate_referral_id
|
||||
belongs_to :affiliate_legalese, :class_name => "JamRuby::AffiliateLegalese", :foreign_key => :legalese_id
|
||||
has_many :sale_line_items, :class_name => 'JamRuby::SaleLineItem', foreign_key: :affiliate_referral_id
|
||||
has_many :quarters, :class_name => 'JamRuby::AffiliateQuarterlyPayment', foreign_key: :affiliate_partner_id, inverse_of: :affiliate_partner
|
||||
has_many :months, :class_name => 'JamRuby::AffiliateMonthlyPayment', foreign_key: :affiliate_partner_id, inverse_of: :affiliate_partner
|
||||
has_many :traffic_totals, :class_name => 'JamRuby::AffiliateTrafficTotal', foreign_key: :affiliate_partner_id, inverse_of: :affiliate_partner
|
||||
has_many :visits, :class_name => 'JamRuby::AffiliateReferralVisit', foreign_key: :affiliate_partner_id, inverse_of: :affiliate_partner
|
||||
attr_accessible :partner_name, :partner_code, :partner_user_id
|
||||
|
||||
ENTITY_TYPES = %w{ Individual Sole\ Proprietor Limited\ Liability\ Company\ (LLC) Partnership Trust/Estate S\ Corporation C\ Corporation Other }
|
||||
|
||||
KEY_ADDR1 = 'address1'
|
||||
KEY_ADDR2 = 'address2'
|
||||
KEY_CITY = 'city'
|
||||
KEY_STATE = 'state'
|
||||
KEY_POSTAL = 'postal_code'
|
||||
KEY_COUNTRY = 'country'
|
||||
|
||||
# ten dollars in cents
|
||||
PAY_THRESHOLD = 10 * 100
|
||||
|
||||
AFFILIATE_PARAMS="utm_source=affiliate&utm_medium=affiliate&utm_campaign=2015-affiliate-custom&affiliate="
|
||||
|
||||
ADDRESS_SCHEMA = {
|
||||
KEY_ADDR1 => '',
|
||||
KEY_ADDR2 => '',
|
||||
KEY_CITY => '',
|
||||
KEY_STATE => '',
|
||||
KEY_POSTAL => '',
|
||||
KEY_COUNTRY => '',
|
||||
}
|
||||
|
||||
PARAM_REFERRAL = :ref
|
||||
PARAM_COOKIE = :affiliate_ref
|
||||
|
||||
PARTNER_CODE_REGEX = /^[#{Regexp.escape('abcdefghijklmnopqrstuvwxyz0123456789-._+,')}]+{2,128}$/i
|
||||
|
||||
validates :user_email, format: {with: JamRuby::User::VALID_EMAIL_REGEX}, :if => :user_email
|
||||
validates :partner_name, presence: true
|
||||
validates :partner_code, presence: true, format: { with: PARTNER_CODE_REGEX }
|
||||
validates :partner_user, presence: true
|
||||
#validates :user_email, format: {with: JamRuby::User::VALID_EMAIL_REGEX}, :if => :user_email
|
||||
#validates :partner_code, format: { with: PARTNER_CODE_REGEX }, :allow_blank => true
|
||||
validates :entity_type, inclusion: {in: ENTITY_TYPES, message: "invalid entity type"}
|
||||
|
||||
serialize :address, JSON
|
||||
|
||||
before_save do |record|
|
||||
record.address ||= ADDRESS_SCHEMA.clone
|
||||
record.entity_type ||= ENTITY_TYPES.first
|
||||
end
|
||||
|
||||
# used by admin
|
||||
def self.create_with_params(params={})
|
||||
raise 'not supported'
|
||||
oo = self.new
|
||||
oo.partner_name = params[:partner_name].try(:strip)
|
||||
oo.partner_code = params[:partner_code].try(:strip).try(:downcase)
|
||||
oo.partner_user = User.where(:email => params[:user_email].try(:strip)).limit(1).first
|
||||
oo.partner_user_id = oo.partner_user.try(:id)
|
||||
oo.entity_type = params[:entity_type] || ENTITY_TYPES.first
|
||||
oo.save
|
||||
oo
|
||||
end
|
||||
|
||||
# used by web
|
||||
def self.create_with_web_params(user, params={})
|
||||
oo = self.new
|
||||
oo.partner_name = params[:partner_name].try(:strip)
|
||||
oo.partner_user = user if user # user is not required
|
||||
oo.entity_type = params[:entity_type] || ENTITY_TYPES.first
|
||||
oo.signed_at = Time.now
|
||||
oo.save
|
||||
oo
|
||||
end
|
||||
|
|
@ -29,16 +79,394 @@ class JamRuby::AffiliatePartner < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def self.is_code?(code)
|
||||
self.where(:partner_code => code).limit(1).pluck(:id).present?
|
||||
self.where(:partner_code => code).limit(1).pluck(:id).present?
|
||||
end
|
||||
|
||||
def referrals_by_date
|
||||
by_date = User.where(:affiliate_referral_id => self.id)
|
||||
.group('DATE(created_at)')
|
||||
.having("COUNT(*) > 0")
|
||||
.order('date_created_at DESC')
|
||||
.count
|
||||
.group('DATE(created_at)')
|
||||
.having("COUNT(*) > 0")
|
||||
.order('date_created_at DESC')
|
||||
.count
|
||||
block_given? ? yield(by_date) : by_date
|
||||
end
|
||||
|
||||
def signed_legalese(legalese)
|
||||
self.affiliate_legalese = legalese
|
||||
self.signed_at = Time.now
|
||||
save!
|
||||
end
|
||||
|
||||
def update_address_value(key, val)
|
||||
self.address[key] = val
|
||||
self.update_attribute(:address, self.address)
|
||||
end
|
||||
|
||||
def address_value(key)
|
||||
self.address[key]
|
||||
end
|
||||
|
||||
def created_within_affiliate_window(user, sale_time)
|
||||
sale_time - user.created_at < 2.years
|
||||
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: real_quantity * 20}
|
||||
else
|
||||
false
|
||||
end
|
||||
else
|
||||
raise 'shopping cart type not implemented yet'
|
||||
end
|
||||
end
|
||||
|
||||
def cumulative_earnings_in_dollars
|
||||
cumulative_earnings_in_cents.to_f / 100.to_f
|
||||
end
|
||||
|
||||
def self.quarter_info(date)
|
||||
|
||||
year = date.year
|
||||
|
||||
# which quarter?
|
||||
quarter = -1
|
||||
if date.month >= 1 && date.month <= 3
|
||||
quarter = 0
|
||||
elsif date.month >= 4 && date.month <= 6
|
||||
quarter = 1
|
||||
elsif date.month >= 7 && date.month <= 9
|
||||
quarter = 2
|
||||
elsif date.month >= 10 && date.month <= 12
|
||||
quarter = 3
|
||||
end
|
||||
|
||||
raise 'quarter should never be -1' if quarter == -1
|
||||
|
||||
previous_quarter = quarter - 1
|
||||
previous_year = date.year
|
||||
if previous_quarter == -1
|
||||
previous_quarter = 3
|
||||
previous_year = year - 1
|
||||
end
|
||||
|
||||
raise 'previous quarter should never be -1' if previous_quarter == -1
|
||||
|
||||
{year: year, quarter: quarter, previous_quarter: previous_quarter, previous_year: previous_year}
|
||||
end
|
||||
|
||||
def self.did_quarter_elapse?(quarter_info, last_tallied_info)
|
||||
if last_tallied_info.nil?
|
||||
true
|
||||
else
|
||||
quarter_info == last_tallied_info
|
||||
end
|
||||
end
|
||||
|
||||
# meant to be run regularly; this routine will make summarized counts in the
|
||||
# AffiliateQuarterlyPayment table
|
||||
# AffiliatePartner.cumulative_earnings_in_cents, AffiliatePartner.referral_user_count
|
||||
def self.tally_up(day)
|
||||
|
||||
AffiliatePartner.transaction do
|
||||
quarter_info = quarter_info(day)
|
||||
last_tallied_info = quarter_info(GenericState.affiliate_tallied_at) if GenericState.affiliate_tallied_at
|
||||
|
||||
quarter_elapsed = did_quarter_elapse?(quarter_info, last_tallied_info)
|
||||
|
||||
if quarter_elapsed
|
||||
tally_monthly_payments(quarter_info[:previous_year], quarter_info[:previous_quarter])
|
||||
tally_quarterly_payments(quarter_info[:previous_year], quarter_info[:previous_quarter])
|
||||
end
|
||||
tally_monthly_payments(quarter_info[:year], quarter_info[:quarter])
|
||||
tally_quarterly_payments(quarter_info[:year], quarter_info[:quarter])
|
||||
|
||||
tally_traffic_totals(GenericState.affiliate_tallied_at, day)
|
||||
|
||||
tally_partner_totals
|
||||
|
||||
state = GenericState.singleton
|
||||
state.affiliate_tallied_at = day
|
||||
state.save!
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# this just makes sure that the quarter rows exist before later manipulations with UPDATEs
|
||||
def self.ensure_quarters_exist(year, quarter)
|
||||
|
||||
sql = %{
|
||||
INSERT INTO affiliate_quarterly_payments (quarter, year, affiliate_partner_id)
|
||||
(SELECT #{quarter}, #{year}, affiliate_partners.id FROM affiliate_partners WHERE affiliate_partners.partner_user_id IS NOT NULL AND affiliate_partners.id NOT IN
|
||||
(SELECT affiliate_partner_id FROM affiliate_quarterly_payments WHERE year = #{year} AND quarter = #{quarter}))
|
||||
}
|
||||
|
||||
ActiveRecord::Base.connection.execute(sql)
|
||||
end
|
||||
|
||||
# this just makes sure that the quarter rows exist before later manipulations with UPDATEs
|
||||
def self.ensure_months_exist(year, quarter)
|
||||
|
||||
months = [1, 2, 3].collect! { |i| quarter * 3 + i }
|
||||
|
||||
months.each do |month|
|
||||
sql = %{
|
||||
INSERT INTO affiliate_monthly_payments (month, year, affiliate_partner_id)
|
||||
(SELECT #{month}, #{year}, affiliate_partners.id FROM affiliate_partners WHERE affiliate_partners.partner_user_id IS NOT NULL AND affiliate_partners.id NOT IN
|
||||
(SELECT affiliate_partner_id FROM affiliate_monthly_payments WHERE year = #{year} AND month = #{month}))
|
||||
}
|
||||
|
||||
ActiveRecord::Base.connection.execute(sql)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def self.sale_items_subquery(start_date, end_date, table_name)
|
||||
%{
|
||||
FROM sale_line_items
|
||||
WHERE
|
||||
(DATE(sale_line_items.created_at) >= DATE('#{start_date}') AND DATE(sale_line_items.created_at) <= DATE('#{end_date}'))
|
||||
AND
|
||||
sale_line_items.affiliate_referral_id = #{table_name}.affiliate_partner_id
|
||||
}
|
||||
end
|
||||
|
||||
def self.sale_items_refunded_subquery(start_date, end_date, table_name)
|
||||
%{
|
||||
FROM sale_line_items
|
||||
WHERE
|
||||
(DATE(sale_line_items.affiliate_refunded_at) >= DATE('#{start_date}') AND DATE(sale_line_items.affiliate_refunded_at) <= DATE('#{end_date}'))
|
||||
AND
|
||||
sale_line_items.affiliate_referral_id = #{table_name}.affiliate_partner_id
|
||||
AND
|
||||
sale_line_items.affiliate_refunded = TRUE
|
||||
}
|
||||
end
|
||||
# total up quarters by looking in sale_line_items for items that are marked as having a affiliate_referral_id
|
||||
# don't forget to substract any sale_line_items that have a affiliate_refunded = TRUE
|
||||
def self.total_months(year, quarter)
|
||||
|
||||
months = [1, 2, 3].collect! { |i| quarter * 3 + i }
|
||||
|
||||
|
||||
months.each do |month|
|
||||
|
||||
start_date, end_date = boundary_dates_for_month(year, month)
|
||||
|
||||
sql = %{
|
||||
UPDATE affiliate_monthly_payments
|
||||
SET
|
||||
last_updated = NOW(),
|
||||
jamtracks_sold =
|
||||
COALESCE(
|
||||
(SELECT COUNT(CASE WHEN sale_line_items.product_type = 'JamTrack' AND sale_line_items.affiliate_referral_fee_in_cents > 0 THEN 1 ELSE NULL END)
|
||||
#{sale_items_subquery(start_date, end_date, 'affiliate_monthly_payments')}
|
||||
), 0)
|
||||
+
|
||||
COALESCE(
|
||||
(SELECT -COUNT(CASE WHEN sale_line_items.product_type = 'JamTrack' AND sale_line_items.affiliate_referral_fee_in_cents > 0 THEN 1 ELSE NULL END)
|
||||
#{sale_items_refunded_subquery(start_date, end_date, 'affiliate_monthly_payments')}
|
||||
), 0),
|
||||
due_amount_in_cents =
|
||||
COALESCE(
|
||||
(SELECT SUM(affiliate_referral_fee_in_cents)
|
||||
#{sale_items_subquery(start_date, end_date, 'affiliate_monthly_payments')}
|
||||
), 0)
|
||||
+
|
||||
COALESCE(
|
||||
(SELECT -SUM(affiliate_referral_fee_in_cents)
|
||||
#{sale_items_refunded_subquery(start_date, end_date, 'affiliate_monthly_payments')}
|
||||
), 0)
|
||||
|
||||
WHERE closed = FALSE AND year = #{year} AND month = #{month}
|
||||
}
|
||||
|
||||
ActiveRecord::Base.connection.execute(sql)
|
||||
end
|
||||
end
|
||||
|
||||
# close any quarters that are done, so we don't manipulate them again
|
||||
def self.close_months(year, quarter)
|
||||
# close any quarters that occurred before this quarter
|
||||
month = quarter * 3 + 1
|
||||
|
||||
sql = %{
|
||||
UPDATE affiliate_monthly_payments
|
||||
SET
|
||||
closed = TRUE, closed_at = NOW()
|
||||
WHERE year < #{year} OR month < #{month}
|
||||
}
|
||||
|
||||
ActiveRecord::Base.connection.execute(sql)
|
||||
|
||||
end
|
||||
|
||||
# total up quarters by looking in sale_line_items for items that are marked as having a affiliate_referral_id
|
||||
# don't forget to substract any sale_line_items that have a affiliate_refunded = TRUE
|
||||
def self.total_quarters(year, quarter)
|
||||
start_date, end_date = boundary_dates(year, quarter)
|
||||
|
||||
sql = %{
|
||||
UPDATE affiliate_quarterly_payments
|
||||
SET
|
||||
last_updated = NOW(),
|
||||
jamtracks_sold =
|
||||
COALESCE(
|
||||
(SELECT COUNT(CASE WHEN sale_line_items.product_type = 'JamTrack' AND sale_line_items.affiliate_referral_fee_in_cents > 0 THEN 1 ELSE NULL END)
|
||||
#{sale_items_subquery(start_date, end_date, 'affiliate_quarterly_payments')}
|
||||
), 0)
|
||||
+
|
||||
COALESCE(
|
||||
(SELECT -COUNT(CASE WHEN sale_line_items.product_type = 'JamTrack' AND sale_line_items.affiliate_referral_fee_in_cents > 0 THEN 1 ELSE NULL END)
|
||||
#{sale_items_refunded_subquery(start_date, end_date, 'affiliate_quarterly_payments')}
|
||||
), 0),
|
||||
due_amount_in_cents =
|
||||
COALESCE(
|
||||
(SELECT SUM(affiliate_referral_fee_in_cents)
|
||||
#{sale_items_subquery(start_date, end_date, 'affiliate_quarterly_payments')}
|
||||
), 0)
|
||||
+
|
||||
COALESCE(
|
||||
(SELECT -SUM(affiliate_referral_fee_in_cents)
|
||||
#{sale_items_refunded_subquery(start_date, end_date, 'affiliate_quarterly_payments')}
|
||||
), 0)
|
||||
|
||||
WHERE closed = FALSE AND paid = FALSE AND year = #{year} AND quarter = #{quarter}
|
||||
}
|
||||
|
||||
ActiveRecord::Base.connection.execute(sql)
|
||||
end
|
||||
|
||||
# close any quarters that are done, so we don't manipulate them again
|
||||
def self.close_quarters(year, quarter)
|
||||
# close any quarters that occurred before this quarter
|
||||
sql = %{
|
||||
UPDATE affiliate_quarterly_payments
|
||||
SET
|
||||
closed = TRUE, closed_at = NOW()
|
||||
WHERE year < #{year} OR quarter < #{quarter}
|
||||
}
|
||||
|
||||
ActiveRecord::Base.connection.execute(sql)
|
||||
end
|
||||
|
||||
def self.tally_quarterly_payments(year, quarter)
|
||||
ensure_quarters_exist(year, quarter)
|
||||
|
||||
total_quarters(year, quarter)
|
||||
|
||||
close_quarters(year, quarter)
|
||||
end
|
||||
|
||||
|
||||
def self.tally_monthly_payments(year, quarter)
|
||||
ensure_months_exist(year, quarter)
|
||||
|
||||
total_months(year, quarter)
|
||||
|
||||
close_months(year, quarter)
|
||||
end
|
||||
|
||||
def self.tally_partner_totals
|
||||
sql = %{
|
||||
UPDATE affiliate_partners SET
|
||||
referral_user_count = (SELECT count(*) FROM users WHERE affiliate_partners.id = users.affiliate_referral_id),
|
||||
cumulative_earnings_in_cents = (SELECT COALESCE(SUM(due_amount_in_cents), 0) FROM affiliate_quarterly_payments AS aqp WHERE aqp.affiliate_partner_id = affiliate_partners.id AND closed = TRUE and paid = TRUE)
|
||||
}
|
||||
ActiveRecord::Base.connection.execute(sql)
|
||||
end
|
||||
|
||||
def self.tally_traffic_totals(last_tallied_at, target_day)
|
||||
|
||||
if last_tallied_at
|
||||
start_date = last_tallied_at.to_date
|
||||
end_date = target_day.to_date
|
||||
else
|
||||
start_date = target_day.to_date - 1
|
||||
end_date = target_day.to_date
|
||||
end
|
||||
|
||||
if start_date == end_date
|
||||
return
|
||||
end
|
||||
|
||||
sql = %{
|
||||
INSERT INTO affiliate_traffic_totals(SELECT day, 0, 0, ap.id FROM affiliate_partners AS ap CROSS JOIN (select (generate_series('#{start_date}', '#{end_date - 1}', '1 day'::interval))::date as day) AS lurp)
|
||||
}
|
||||
ActiveRecord::Base.connection.execute(sql)
|
||||
|
||||
sql = %{
|
||||
UPDATE affiliate_traffic_totals traffic SET visits = COALESCE((SELECT COALESCE(count(affiliate_partner_id), 0) FROM affiliate_referral_visits v WHERE DATE(v.created_at) >= DATE('#{start_date}') AND DATE(v.created_at) < DATE('#{end_date}') AND v.created_at::date = traffic.day AND v.affiliate_partner_id = traffic.affiliate_partner_id GROUP BY affiliate_partner_id, v.created_at::date ), 0) WHERE traffic.day >= DATE('#{start_date}') AND traffic.day < DATE('#{end_date}')
|
||||
}
|
||||
ActiveRecord::Base.connection.execute(sql)
|
||||
|
||||
sql = %{
|
||||
UPDATE affiliate_traffic_totals traffic SET signups = COALESCE((SELECT COALESCE(count(v.id), 0) FROM users v WHERE DATE(v.created_at) >= DATE('#{start_date}') AND DATE(v.created_at) < DATE('#{end_date}') AND v.created_at::date = traffic.day AND v.affiliate_referral_id = traffic.affiliate_partner_id GROUP BY affiliate_referral_id, v.created_at::date ), 0) WHERE traffic.day >= DATE('#{start_date}') AND traffic.day < DATE('#{end_date}')
|
||||
}
|
||||
ActiveRecord::Base.connection.execute(sql)
|
||||
end
|
||||
|
||||
def self.boundary_dates(year, quarter)
|
||||
if quarter == 0
|
||||
[Date.new(year, 1, 1), Date.new(year, 3, 31)]
|
||||
elsif quarter == 1
|
||||
[Date.new(year, 4, 1), Date.new(year, 6, 30)]
|
||||
elsif quarter == 2
|
||||
[Date.new(year, 7, 1), Date.new(year, 9, 30)]
|
||||
elsif quarter == 3
|
||||
[Date.new(year, 10, 1), Date.new(year, 12, 31)]
|
||||
else
|
||||
raise "invalid quarter #{quarter}"
|
||||
end
|
||||
end
|
||||
|
||||
# 1-based month
|
||||
def self.boundary_dates_for_month(year, month)
|
||||
[Date.new(year, month, 1), Date.civil(year, month, -1)]
|
||||
end
|
||||
|
||||
# Finds all affiliates that need to be paid
|
||||
def self.unpaid
|
||||
|
||||
joins(:quarters)
|
||||
.where('affiliate_quarterly_payments.paid = false').where('affiliate_quarterly_payments.closed = true')
|
||||
.group('affiliate_partners.id')
|
||||
.having('sum(due_amount_in_cents) >= ?', PAY_THRESHOLD)
|
||||
.order('sum(due_amount_in_cents) DESC')
|
||||
|
||||
end
|
||||
|
||||
# does this one affiliate need to be paid?
|
||||
def unpaid
|
||||
due_amount_in_cents > PAY_THRESHOLD
|
||||
end
|
||||
|
||||
# admin function: mark the affiliate paid
|
||||
def mark_paid
|
||||
if unpaid
|
||||
transaction do
|
||||
now = Time.now
|
||||
quarters.where(paid:false, closed:true).update_all(paid:true, paid_at: now)
|
||||
self.last_paid_at = now
|
||||
self.save!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# how much is this affiliate due?
|
||||
def due_amount_in_cents
|
||||
total_in_cents = 0
|
||||
quarters.where(paid:false, closed:true).each do |quarter|
|
||||
total_in_cents = total_in_cents + quarter.due_amount_in_cents
|
||||
end
|
||||
total_in_cents
|
||||
end
|
||||
|
||||
def affiliate_query_params
|
||||
AffiliatePartner::AFFILIATE_PARAMS + self.id.to_s
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
module JamRuby
|
||||
class AffiliatePayment < ActiveRecord::Base
|
||||
|
||||
belongs_to :affiliate_monthly_payment
|
||||
belongs_to :affiliate_quarterly_payment
|
||||
|
||||
def self.index(user, options)
|
||||
|
||||
unless user.affiliate_partner
|
||||
return [[], nil]
|
||||
end
|
||||
|
||||
affiliate_partner_id = user.affiliate_partner.id
|
||||
|
||||
|
||||
page = options[:page].to_i
|
||||
per_page = options[:per_page].to_i
|
||||
|
||||
if page == 0
|
||||
page = 1
|
||||
end
|
||||
|
||||
if per_page == 0
|
||||
per_page = 50
|
||||
end
|
||||
|
||||
start = (page -1 ) * per_page
|
||||
limit = per_page
|
||||
|
||||
|
||||
query = AffiliatePayment
|
||||
.includes(affiliate_quarterly_payment: [], affiliate_monthly_payment:[])
|
||||
.where(affiliate_partner_id: affiliate_partner_id)
|
||||
.where("(payment_type='quarterly' AND closed = true) OR payment_type='monthly'")
|
||||
.where('(paid = TRUE or due_amount_in_cents < 10000 or paid is NULL)')
|
||||
.paginate(:page => page, :per_page => limit)
|
||||
.order('year ASC, time_sort ASC, payment_type ASC')
|
||||
|
||||
if query.length == 0
|
||||
[query, nil]
|
||||
elsif query.length < limit
|
||||
[query, nil]
|
||||
else
|
||||
[query, start + limit]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
class JamRuby::AffiliateQuarterlyPayment < ActiveRecord::Base
|
||||
|
||||
belongs_to :affiliate_partner, class_name: 'JamRuby::AffiliatePartner', inverse_of: :quarters
|
||||
|
||||
|
||||
def self.index(user, options)
|
||||
unless user.affiliate_partner
|
||||
return [[], nil]
|
||||
end
|
||||
|
||||
page = options[:page].to_i
|
||||
per_page = options[:per_page].to_i
|
||||
|
||||
if page == 0
|
||||
page = 1
|
||||
end
|
||||
|
||||
if per_page == 0
|
||||
per_page = 50
|
||||
end
|
||||
|
||||
start = (page -1 ) * per_page
|
||||
limit = per_page
|
||||
|
||||
query = AffiliateQuarterlyPayment
|
||||
.paginate(page: page, per_page: per_page)
|
||||
.where(affiliate_partner_id: user.affiliate_partner.id)
|
||||
.where(closed:true)
|
||||
.where(paid:true)
|
||||
.order('year ASC, quarter ASC')
|
||||
|
||||
if query.length == 0
|
||||
[query, nil]
|
||||
elsif query.length < limit
|
||||
[query, nil]
|
||||
else
|
||||
[query, start + limit]
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
class JamRuby::AffiliateReferralVisit < ActiveRecord::Base
|
||||
|
||||
belongs_to :affiliate_partner, class_name: 'JamRuby::AffiliatePartner', inverse_of: :visits
|
||||
|
||||
validates :affiliate_partner_id, numericality: {only_integer: true}, :allow_nil => true
|
||||
validates :visited_url, length: {maximum: 1000}
|
||||
validates :referral_url, length: {maximum: 1000}
|
||||
validates :ip_address, presence: true, length: {maximum:1000}
|
||||
validates :first_visit, inclusion: {in: [true, false]}
|
||||
validates :user_id, length: {maximum:64}
|
||||
|
||||
def self.track(options = {})
|
||||
visit = AffiliateReferralVisit.new
|
||||
visit.affiliate_partner_id = options[:affiliate_id]
|
||||
visit.ip_address = options[:remote_ip]
|
||||
visit.visited_url = options[:visited_url]
|
||||
visit.referral_url = options[:referral_url]
|
||||
visit.first_visit = options[:visited].nil?
|
||||
visit.user_id = options[:current_user].id if options[:current_user]
|
||||
visit.save
|
||||
visit
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
class JamRuby::AffiliateTrafficTotal < ActiveRecord::Base
|
||||
|
||||
belongs_to :affiliate_partner, class_name: 'JamRuby::AffiliatePartner', inverse_of: :traffic_totals
|
||||
|
||||
|
||||
def self.index(user, options)
|
||||
unless user.affiliate_partner
|
||||
return [[], nil]
|
||||
end
|
||||
|
||||
page = options[:page].to_i
|
||||
per_page = options[:per_page].to_i
|
||||
|
||||
if page == 0
|
||||
page = 1
|
||||
end
|
||||
|
||||
if per_page == 0
|
||||
per_page = 50
|
||||
end
|
||||
|
||||
start = (page -1 ) * per_page
|
||||
limit = per_page
|
||||
|
||||
query = AffiliateTrafficTotal
|
||||
.paginate(page: page, per_page: per_page)
|
||||
.where(affiliate_partner_id: user.affiliate_partner.id)
|
||||
.where('visits != 0 OR signups != 0')
|
||||
.order('day ASC')
|
||||
|
||||
if query.length == 0
|
||||
[query, nil]
|
||||
elsif query.length < limit
|
||||
[query, nil]
|
||||
else
|
||||
[query, start + limit]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -4,14 +4,15 @@
|
|||
module JamRuby
|
||||
class AnonymousUser
|
||||
|
||||
attr_accessor :id
|
||||
attr_accessor :id, :cookies
|
||||
|
||||
def initialize(id)
|
||||
def initialize(id, cookies)
|
||||
@id = id
|
||||
@cookies = cookies
|
||||
end
|
||||
|
||||
def shopping_carts
|
||||
ShoppingCart.where(anonymous_user_id: @id)
|
||||
ShoppingCart.where(anonymous_user_id: @id).order('created_at DESC')
|
||||
end
|
||||
|
||||
def destroy_all_shopping_carts
|
||||
|
|
@ -23,7 +24,11 @@ module JamRuby
|
|||
end
|
||||
|
||||
def has_redeemable_jamtrack
|
||||
true
|
||||
APP_CONFIG.one_free_jamtrack_per_user && !@cookies[:redeemed_jamtrack]
|
||||
end
|
||||
|
||||
def signup_hint
|
||||
SignupHint.where(anonymous_user_id: @id).where('expires_at > ?', Time.now).first
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ module JamRuby
|
|||
# this is basically a dev-time only path of code; we store real artifacts in s3
|
||||
url = APP_CONFIG.jam_admin_root_url + self.uri.url
|
||||
else
|
||||
url = "http://#{APP_CONFIG.cloudfront_host}/#{self.uri.store_dir}/#{self[:uri]}"
|
||||
url = "https://#{APP_CONFIG.cloudfront_host}/#{self.uri.store_dir}/#{self[:uri]}"
|
||||
#url = self.uri.url.gsub(APP_CONFIG.aws_fullhost, APP_CONFIG.cloudfront_host)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -163,14 +163,14 @@ module JamRuby
|
|||
|
||||
# ensure person creating this Band is a Musician
|
||||
unless user.musician?
|
||||
raise PermissionError, "must be a musician"
|
||||
raise JamPermissionError, "must be a musician"
|
||||
end
|
||||
|
||||
band = id.blank? ? Band.new : Band.find(id)
|
||||
|
||||
# ensure user updating Band details is a Band member
|
||||
unless band.new_record? || band.users.exists?(user)
|
||||
raise PermissionError, ValidationMessages::USER_NOT_BAND_MEMBER_VALIDATION_ERROR
|
||||
raise JamPermissionError, ValidationMessages::USER_NOT_BAND_MEMBER_VALIDATION_ERROR
|
||||
end
|
||||
|
||||
band.name = params[:name] if params.has_key?(:name)
|
||||
|
|
@ -224,8 +224,8 @@ module JamRuby
|
|||
:cropped_s3_path_photo => cropped_s3_path,
|
||||
:cropped_large_s3_path_photo => cropped_large_s3_path,
|
||||
:crop_selection_photo => crop_selection,
|
||||
:photo_url => S3Util.url(aws_bucket, escape_filename(cropped_s3_path), :secure => false),
|
||||
:large_photo_url => S3Util.url(aws_bucket, escape_filename(cropped_large_s3_path), :secure => false))
|
||||
:photo_url => S3Util.url(aws_bucket, escape_filename(cropped_s3_path), :secure => true),
|
||||
:large_photo_url => S3Util.url(aws_bucket, escape_filename(cropped_large_s3_path), :secure => true))
|
||||
end
|
||||
|
||||
def delete_photo(aws_bucket)
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ module JamRuby
|
|||
# params is a hash, and everything is optional
|
||||
def update_fields(user, params)
|
||||
if user != self.user
|
||||
raise PermissionError, "user doesn't own claimed_recording"
|
||||
raise JamPermissionError, "user doesn't own claimed_recording"
|
||||
end
|
||||
|
||||
self.name = params[:name]
|
||||
|
|
@ -52,7 +52,7 @@ module JamRuby
|
|||
|
||||
def discard(user)
|
||||
if user != self.user
|
||||
raise PermissionError, "user doesn't own claimed_recording"
|
||||
raise JamPermissionError, "user doesn't own claimed_recording"
|
||||
end
|
||||
|
||||
ClaimedRecording.where(:id => id).update_all(:discarded => true )
|
||||
|
|
@ -95,11 +95,11 @@ module JamRuby
|
|||
|
||||
target_user = params[:user]
|
||||
|
||||
raise PermissionError, "must specify current user" unless user
|
||||
raise JamPermissionError, "must specify current user" unless user
|
||||
raise "user must be specified" unless target_user
|
||||
|
||||
if target_user != user.id
|
||||
raise PermissionError, "unable to view another user's favorites"
|
||||
raise JamPermissionError, "unable to view another user's favorites"
|
||||
end
|
||||
|
||||
query = ClaimedRecording.limit(limit).order('created_at DESC').offset(start)
|
||||
|
|
|
|||
|
|
@ -24,11 +24,12 @@ module JamRuby
|
|||
validates :metronome_open, :inclusion => {:in => [true, false]}
|
||||
validates :as_musician, :inclusion => {:in => [true, false, nil]}
|
||||
validates :client_type, :inclusion => {:in => CLIENT_TYPES}
|
||||
validates_numericality_of :last_jam_audio_latency, greater_than:0, :allow_nil => true
|
||||
validates_numericality_of :last_jam_audio_latency, greater_than: 0, :allow_nil => true
|
||||
validate :can_join_music_session, :if => :joining_session?
|
||||
validate :user_or_latency_tester_present
|
||||
|
||||
after_save :require_at_least_one_track_when_in_session, :if => :joining_session?
|
||||
# this is no longer required with the new no-input profile
|
||||
#after_save :require_at_least_one_track_when_in_session, :if => :joining_session?
|
||||
after_create :did_create
|
||||
after_save :report_add_participant
|
||||
|
||||
|
|
@ -62,11 +63,11 @@ module JamRuby
|
|||
def state_message
|
||||
case self.aasm_state.to_sym
|
||||
when CONNECT_STATE
|
||||
'Connected'
|
||||
when STALE_STATE
|
||||
'Stale'
|
||||
'Connected'
|
||||
when STALE_STATE
|
||||
'Stale'
|
||||
else
|
||||
'Idle'
|
||||
'Idle'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -85,7 +86,7 @@ module JamRuby
|
|||
def joining_session?
|
||||
joining_session
|
||||
end
|
||||
|
||||
|
||||
def can_join_music_session
|
||||
|
||||
# puts "can_join_music_session: #{music_session_id} was #{music_session_id_was}" if music_session_id_changed?
|
||||
|
|
@ -183,8 +184,8 @@ module JamRuby
|
|||
end
|
||||
|
||||
def associate_tracks(tracks)
|
||||
self.tracks.clear()
|
||||
unless tracks.nil?
|
||||
self.tracks.clear()
|
||||
tracks.each do |track|
|
||||
t = Track.new
|
||||
t.instrument = Instrument.find(track["instrument_id"])
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
module JamRuby
|
||||
class FingerprintWhitelist < ActiveRecord::Base
|
||||
|
||||
@@log = Logging.logger[FingerprintWhitelist]
|
||||
|
||||
validates :fingerprint, presence: true, uniqueness: true
|
||||
has_many :machine_fingerprint, class_name: 'JamRuby::MachineFingerprint', foreign_key: :fingerprint
|
||||
|
||||
def admin_url
|
||||
APP_CONFIG.admin_root_url + "/admin/fingerprint_whitelists/" + id
|
||||
end
|
||||
|
||||
def to_s
|
||||
"#{fingerprint}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
module JamRuby
|
||||
class FraudAlert < ActiveRecord::Base
|
||||
|
||||
@@log = Logging.logger[MachineExtra]
|
||||
|
||||
belongs_to :machine_fingerprint, :class_name => "JamRuby::MachineFingerprint"
|
||||
belongs_to :user, :class_name => "JamRuby::User"
|
||||
|
||||
|
||||
def self.create(machine_fingerprint, user)
|
||||
fraud = FraudAlert.new
|
||||
fraud.machine_fingerprint = machine_fingerprint
|
||||
fraud.user = user
|
||||
fraud.save
|
||||
|
||||
unless fraud.save
|
||||
@@log.error("unable to create fraud: #{fraud.errors.inspect}")
|
||||
end
|
||||
fraud
|
||||
end
|
||||
|
||||
def admin_url
|
||||
APP_CONFIG.admin_root_url + "/admin/fraud_alerts/" + id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -23,9 +23,14 @@ module JamRuby
|
|||
(database_environment == 'development' && Environment.mode == 'development')
|
||||
end
|
||||
|
||||
def self.affiliate_tallied_at
|
||||
GenericState.singleton.affiliate_tallied_at
|
||||
end
|
||||
|
||||
def self.singleton
|
||||
GenericState.find('default')
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ module JamRuby
|
|||
mount.source_pass = APP_CONFIG.icecast_hardcoded_source_password || SecureRandom.urlsafe_base64
|
||||
mount.stream_name = "JamKazam music session created by #{music_session.creator.name}"
|
||||
mount.stream_description = music_session.description
|
||||
mount.stream_url = "http://www.jamkazam.com" ## TODO/XXX, the jamkazam url should be the page hosting the widget
|
||||
mount.stream_url = "https://www.jamkazam.com" ## TODO/XXX, the jamkazam url should be the page hosting the widget
|
||||
mount.genre = music_session.genre.description
|
||||
mount
|
||||
end
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ module JamRuby
|
|||
:original_artist, :songwriter, :publisher, :licensor, :licensor_id, :pro, :genre, :genre_id, :sales_region, :price,
|
||||
: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, :version, :jmep_json, :jmep_text, :pro_ascap, :pro_bmi, :pro_sesac, as: :admin
|
||||
:jam_track_tap_ins_attributes, :version, :jmep_json, :jmep_text, :pro_ascap, :pro_bmi, :pro_sesac, :duration, as: :admin
|
||||
|
||||
validates :name, presence: true, uniqueness: true, length: {maximum: 200}
|
||||
validates :plan_code, presence: true, uniqueness: true, length: {maximum: 50 }
|
||||
|
|
@ -60,7 +60,10 @@ module JamRuby
|
|||
# has_many :plays, :class_name => "JamRuby::PlayablePlay", :foreign_key => :jam_track_id, :dependent => :destroy
|
||||
# VRFS-2916 jam_tracks.id is varchar: ADD
|
||||
has_many :plays, :class_name => "JamRuby::PlayablePlay", :as => :playable, :dependent => :destroy
|
||||
|
||||
|
||||
# when we know what JamTrack this refund is related to, these are associated
|
||||
belongs_to :recurly_transactions, class_name: 'JamRuby::RecurlyTransactionWebHook'
|
||||
|
||||
accepts_nested_attributes_for :jam_track_tracks, allow_destroy: true
|
||||
accepts_nested_attributes_for :jam_track_tap_ins, allow_destroy: true
|
||||
|
||||
|
|
@ -100,6 +103,7 @@ module JamRuby
|
|||
warnings << 'POSITIONS' if duplicate_positions?
|
||||
warnings << 'PREVIEWS'if missing_previews?
|
||||
warnings << 'DURATION' if duration.nil?
|
||||
warnings << 'JMEP' if jmep_json.blank?
|
||||
warnings.join(',')
|
||||
end
|
||||
|
||||
|
|
@ -112,6 +116,7 @@ module JamRuby
|
|||
def all_artists
|
||||
JamTrack.select("original_artist").
|
||||
group("original_artist").
|
||||
order('original_artist').
|
||||
collect{|jam_track|jam_track.original_artist}
|
||||
end
|
||||
|
||||
|
|
@ -161,16 +166,75 @@ module JamRuby
|
|||
query = query.where("original_artist=?", options[:artist])
|
||||
end
|
||||
|
||||
if options[:group_artist]
|
||||
query = query.group("original_artist")
|
||||
if options[:id].present?
|
||||
query = query.where("jam_tracks.id=?", options[:id])
|
||||
end
|
||||
|
||||
|
||||
if options[:group_artist]
|
||||
query = query.select("original_artist, array_agg(jam_tracks.id) AS id, MIN(name) AS name, MIN(description) AS description, MIN(recording_type) AS recording_type, MIN(original_artist) AS original_artist, MIN(songwriter) AS songwriter, MIN(publisher) AS publisher, MIN(sales_region) AS sales_region, MIN(price) AS price, MIN(version) AS version, MIN(genre_id) AS genre_id")
|
||||
query = query.group("original_artist")
|
||||
query = query.order('jam_tracks.original_artist')
|
||||
else
|
||||
query = query.group("jam_tracks.id")
|
||||
query = query.order('jam_tracks.original_artist, jam_tracks.name')
|
||||
end
|
||||
|
||||
query = query.where("jam_tracks.status = ?", 'Production') unless user.admin
|
||||
query = query.where("jam_tracks.genre_id = '#{options[:genre]}'") unless options[:genre].blank?
|
||||
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_tracks.sales_region = '#{options[:availability]}'") unless options[:availability].blank?
|
||||
|
||||
|
||||
if query.length == 0
|
||||
[query, nil]
|
||||
elsif query.length < limit
|
||||
[query, nil]
|
||||
else
|
||||
[query, start + limit]
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# provides artist names and how many jamtracks are available for each
|
||||
def artist_index(options, user)
|
||||
if options[:page]
|
||||
page = options[:page].to_i
|
||||
per_page = options[:per_page].to_i
|
||||
|
||||
if per_page == 0
|
||||
# try and see if limit was specified
|
||||
limit = options[:limit]
|
||||
limit ||= 100
|
||||
limit = limit.to_i
|
||||
else
|
||||
limit = per_page
|
||||
end
|
||||
|
||||
start = (page -1 )* per_page
|
||||
limit = per_page
|
||||
else
|
||||
limit = options[:limit]
|
||||
limit ||= 100
|
||||
limit = limit.to_i
|
||||
|
||||
start = options[:start].presence
|
||||
start = start.to_i || 0
|
||||
|
||||
page = 1 + start/limit
|
||||
per_page = limit
|
||||
end
|
||||
|
||||
|
||||
query = JamTrack.paginate(page: page, per_page: per_page)
|
||||
query = query.select("original_artist, count(original_artist) AS song_count")
|
||||
query = query.group("original_artist")
|
||||
query = query.order('jam_tracks.original_artist')
|
||||
|
||||
query = query.where("jam_tracks.status = ?", 'Production') unless user.admin
|
||||
query = query.where("jam_tracks.genre_id = '#{options[:genre]}'") unless options[:genre].blank?
|
||||
query = query.where("jam_track_tracks.instrument_id = '#{options[:instrument]}'") unless options[:instrument].blank?
|
||||
query = query.where("jam_tracks.sales_region = '#{options[:availability]}'") unless options[:availability].blank?
|
||||
query = query.group("jam_tracks.id")
|
||||
query = query.order('jam_tracks.name')
|
||||
|
||||
|
||||
if query.length == 0
|
||||
[query, nil]
|
||||
|
|
@ -187,6 +251,10 @@ module JamRuby
|
|||
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')
|
||||
end
|
||||
|
||||
def can_download?(user)
|
||||
owners.include?(user)
|
||||
end
|
||||
|
|
@ -194,5 +262,11 @@ module JamRuby
|
|||
def right_for_user(user)
|
||||
jam_track_rights.where("user_id=?", user).first
|
||||
end
|
||||
|
||||
def short_plan_code
|
||||
prefix = 'jamtrack-'
|
||||
plan_code[prefix.length..-1]
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,21 +3,24 @@ module JamRuby
|
|||
# describes what users have rights to which tracks
|
||||
class JamTrackRight < ActiveRecord::Base
|
||||
include JamRuby::S3ManagerMixin
|
||||
attr_accessible :user, :jam_track, :user_id, :jam_track_id, :download_count
|
||||
|
||||
@@log = Logging.logger[JamTrackRight]
|
||||
|
||||
attr_accessible :user, :jam_track, :user_id, :jam_track_id, :download_count
|
||||
attr_accessible :user_id, :jam_track_id, as: :admin
|
||||
attr_accessible :url_48, :md5_48, :length_48, :url_44, :md5_44, :length_44
|
||||
belongs_to :user, class_name: "JamRuby::User" # the owner, or purchaser of the jam_track
|
||||
belongs_to :user, class_name: "JamRuby::User" # the owner, or purchaser of the jam_track
|
||||
belongs_to :jam_track, class_name: "JamRuby::JamTrack"
|
||||
|
||||
validates :user, presence:true
|
||||
validates :jam_track, presence:true
|
||||
validates :is_test_purchase, inclusion: {in: [true, false]}
|
||||
validates :user, presence: true
|
||||
validates :jam_track, presence: true
|
||||
validates :is_test_purchase, inclusion: {in: [true, false]}
|
||||
|
||||
validate :verify_download_count
|
||||
after_save :after_save
|
||||
|
||||
validates_uniqueness_of :user_id, scope: :jam_track_id
|
||||
|
||||
validates_uniqueness_of :user_id, scope: :jam_track_id
|
||||
|
||||
# Uploads the JKZ:
|
||||
mount_uploader :url_48, JamTrackRightUploader
|
||||
mount_uploader :url_44, JamTrackRightUploader
|
||||
|
|
@ -29,7 +32,7 @@ module JamRuby
|
|||
# try to catch major transitions:
|
||||
|
||||
# if just queue time changes, start time changes, or signed time changes, send out a notice
|
||||
if signing_queued_at_was != signing_queued_at || signing_started_at_was != signing_started_at || last_signed_at_was != last_signed_at
|
||||
if signing_queued_at_was != signing_queued_at || signing_started_at_48_was != signing_started_at_48 || signing_started_at_44_was != signing_started_at_44 || last_signed_at_was != last_signed_at || current_packaging_step != current_packaging_step_was || packaging_steps != packaging_steps_was
|
||||
SubscriptionMessage.jam_track_signing_job_change(self)
|
||||
end
|
||||
end
|
||||
|
|
@ -39,10 +42,10 @@ module JamRuby
|
|||
end
|
||||
|
||||
# create name of the file
|
||||
def filename
|
||||
"#{jam_track.name}.jkz"
|
||||
def filename(bitrate)
|
||||
"#{jam_track.name}-#{bitrate == :url_48 ? '48' : '44'}.jkz"
|
||||
end
|
||||
|
||||
|
||||
def verify_download_count
|
||||
if (self.download_count < 0 || self.download_count > MAX_JAM_TRACK_DOWNLOADS) && !@current_user.admin
|
||||
errors.add(:download_count, "must be less than or equal to #{MAX_JAM_TRACK_DOWNLOADS}")
|
||||
|
|
@ -50,15 +53,21 @@ module JamRuby
|
|||
end
|
||||
|
||||
def self.ready_to_clean
|
||||
JamTrackRight.where("downloaded_since_sign=? AND updated_at <= ?", true, 5.minutes.ago).limit(1000)
|
||||
JamTrackRight.where("downloaded_since_sign=? AND updated_at <= ?", true, 5.minutes.ago).limit(1000)
|
||||
end
|
||||
|
||||
def finish_errored(error_reason, error_detail)
|
||||
def finish_errored(error_reason, error_detail, sample_rate)
|
||||
self.last_signed_at = Time.now
|
||||
self.error_count = self.error_count + 1
|
||||
self.error_reason = error_reason
|
||||
self.error_detail = error_detail
|
||||
self.should_retry = self.error_count < 5
|
||||
if sample_rate == 48
|
||||
self.signing_48 = false
|
||||
else
|
||||
self.signing_44 = false
|
||||
end
|
||||
|
||||
if save
|
||||
Notification.send_jam_track_sign_failed(self)
|
||||
else
|
||||
|
|
@ -71,20 +80,19 @@ module JamRuby
|
|||
if bitrate==48
|
||||
self.length_48 = length
|
||||
self.md5_48 = md5
|
||||
self.signed_48 = true
|
||||
self.signing_48 = false
|
||||
else
|
||||
self.length_44 = length
|
||||
self.md5_44 = md5
|
||||
self.signed_44 = true
|
||||
self.signing_44 = false
|
||||
end
|
||||
self.signed = true
|
||||
self.error_count = 0
|
||||
self.error_reason = nil
|
||||
self.error_detail = nil
|
||||
self.should_retry = false
|
||||
if save
|
||||
Notification.send_jam_track_sign_complete(self)
|
||||
else
|
||||
raise "Error sending notification #{self.errors}"
|
||||
end
|
||||
save!
|
||||
end
|
||||
|
||||
# creates a short-lived URL that has access to the object.
|
||||
|
|
@ -93,18 +101,18 @@ module JamRuby
|
|||
# but the url is short lived enough so that it wouldn't be easily shared
|
||||
def sign_url(expiration_time = 120, bitrate=48)
|
||||
field_name = (bitrate==48) ? "url_48" : "url_44"
|
||||
s3_manager.sign_url(self[field_name], {:expires => expiration_time, :secure => false})
|
||||
s3_manager.sign_url(self[field_name], {:expires => expiration_time, :secure => true})
|
||||
end
|
||||
|
||||
def delete_s3_files
|
||||
remove_url_48!
|
||||
remove_url_44!
|
||||
remove_url_48!
|
||||
remove_url_44!
|
||||
end
|
||||
|
||||
|
||||
def enqueue(sample_rate=48)
|
||||
begin
|
||||
JamTrackRight.where(:id => self.id).update_all(:signing_queued_at => Time.now, :signing_started_at => nil, :last_signed_at => nil)
|
||||
JamTrackRight.where(:id => self.id).update_all(:signing_queued_at => Time.now, :signing_started_at_44 => nil, :signing_started_at_48 => nil, :last_signed_at => nil)
|
||||
Resque.enqueue(JamTracksBuilder, self.id, sample_rate)
|
||||
true
|
||||
rescue Exception => e
|
||||
|
|
@ -116,7 +124,7 @@ module JamRuby
|
|||
|
||||
# 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)
|
||||
state = signing_state
|
||||
state = signing_state(sample_rate)
|
||||
if state == 'SIGNED' || state == 'SIGNING' || state == 'QUEUED'
|
||||
false
|
||||
else
|
||||
|
|
@ -129,9 +137,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 && self.url_48.present? && self.url_48.file.exists?
|
||||
self.signed_48 && self.url_48.present? && self.url_48.file.exists?
|
||||
else
|
||||
self.signed && self.url_44.present? && self.url_44.file.exists?
|
||||
self.signed_44 && self.url_44.present? && self.url_44.file.exists?
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -143,12 +151,28 @@ module JamRuby
|
|||
# QUEUED_TIMEOUT - the package signing job (JamTrackBuilder) was queued, but never executed
|
||||
# QUEUED - the package is queued to sign
|
||||
# QUIET - the jam_track_right exists, but no job has been kicked off; a job needs to be enqueued
|
||||
def signing_state
|
||||
def signing_state(sample_rate = nil)
|
||||
state = nil
|
||||
|
||||
# if the caller did not specified sample rate, we will determine what signing state to check by looking at the most recent signing attempt
|
||||
if sample_rate.nil?
|
||||
# determine what package is being signed by checking the most recent signing_started at
|
||||
time_48 = signing_started_at_48.to_i
|
||||
time_44 = signing_started_at_44.to_i
|
||||
sample_rate = time_48 > time_44 ? 48 : 44
|
||||
end
|
||||
|
||||
signed = sample_rate == 48 ? signed_48 : signed_44
|
||||
signing_started_at = sample_rate == 48 ? signing_started_at_48 : signing_started_at_44
|
||||
|
||||
if signed
|
||||
state = 'SIGNED'
|
||||
elsif signing_started_at
|
||||
if Time.now - signing_started_at > APP_CONFIG.signing_job_run_max_time
|
||||
# the maximum amount of time the packaging job can take is 10 seconds * num steps. For a 10 track song, this will be 110 seconds. It's a bit long.
|
||||
signing_job_run_max_time = packaging_steps * 10
|
||||
if Time.now - signing_started_at > signing_job_run_max_time
|
||||
state = 'SIGNING_TIMEOUT'
|
||||
elsif Time.now - last_step_at > APP_CONFIG.signing_step_max_time
|
||||
state = 'SIGNING_TIMEOUT'
|
||||
else
|
||||
state = 'SIGNING'
|
||||
|
|
@ -166,9 +190,18 @@ module JamRuby
|
|||
end
|
||||
state
|
||||
end
|
||||
|
||||
def signed?(sample_rate)
|
||||
sample_rate == 48 ? signed_48 : signed_44
|
||||
end
|
||||
|
||||
def update_download_count(count=1)
|
||||
self.download_count = self.download_count + count
|
||||
self.last_downloaded_at = Time.now
|
||||
|
||||
if self.signed_44 || self.signed_48
|
||||
self.downloaded_since_sign = true
|
||||
end
|
||||
end
|
||||
|
||||
def self.list_keys(user, jamtracks)
|
||||
|
|
@ -176,10 +209,144 @@ module JamRuby
|
|||
return []
|
||||
end
|
||||
|
||||
JamTrack.select('jam_tracks.id, jam_track_rights.private_key AS private_key, jam_track_rights.id AS jam_track_right_id')
|
||||
JamTrack.select('jam_tracks.id, jam_track_rights.private_key_44 AS private_key_44, jam_track_rights.private_key_48 AS private_key_48, jam_track_rights.id AS jam_track_right_id')
|
||||
.joins("LEFT OUTER JOIN jam_track_rights ON jam_tracks.id = jam_track_rights.jam_track_id AND jam_track_rights.user_id = '#{user.id}'")
|
||||
.where('jam_tracks.id IN (?)', jamtracks)
|
||||
end
|
||||
|
||||
|
||||
def guard_against_fraud(current_user, fingerprint, remote_ip)
|
||||
|
||||
if current_user.blank?
|
||||
return "no user specified"
|
||||
end
|
||||
|
||||
# admin's get to skip fraud check
|
||||
if current_user.admin
|
||||
return nil
|
||||
end
|
||||
|
||||
if fingerprint.nil? || fingerprint.empty?
|
||||
return "no fingerprint specified"
|
||||
end
|
||||
|
||||
all_fingerprint = fingerprint.delete(:all)
|
||||
running_fingerprint = fingerprint.delete(:running)
|
||||
|
||||
if all_fingerprint.blank?
|
||||
return "no all fingerprint specified"
|
||||
end
|
||||
|
||||
if running_fingerprint.blank?
|
||||
return "no running fingerprint specified"
|
||||
end
|
||||
|
||||
all_fingerprint_extra = fingerprint[all_fingerprint]
|
||||
running_fingerprint_extra = fingerprint[running_fingerprint]
|
||||
|
||||
if redeemed && !redeemed_and_fingerprinted
|
||||
# if this is a free JamTrack, we need to check for fraud or accidental misuse
|
||||
|
||||
# first of all, does this user have any other JamTracks aside from this one that have already been redeemed it and are marked free?
|
||||
other_redeemed_freebie = JamTrackRight.where(redeemed: true).where(redeemed_and_fingerprinted: true).where('id != ?', id).where(user_id: current_user.id).first
|
||||
|
||||
if other_redeemed_freebie
|
||||
return "already redeemed another"
|
||||
end
|
||||
|
||||
if FingerprintWhitelist.select('id').find_by_fingerprint(all_fingerprint)
|
||||
# we can short circuit out of the rest of the check, since this is a known bad fingerprint
|
||||
@@log.debug("ignoring 'all' hash found in whitelist")
|
||||
else
|
||||
# can we find a jam track that belongs to someone else with the same fingerprint
|
||||
conflict = MachineFingerprint.select('count(id) as count').where('user_id != ?', current_user.id).where(fingerprint: all_fingerprint).where(remote_ip: remote_ip).where('created_at > ?', APP_CONFIG.expire_fingerprint_days.days.ago).first
|
||||
conflict_count = conflict['count'].to_i
|
||||
|
||||
if conflict_count >= APP_CONFIG.found_conflict_count
|
||||
mf = MachineFingerprint.create(all_fingerprint, current_user, MachineFingerprint::TAKEN_ON_FRAUD_CONFLICT, MachineFingerprint::PRINT_TYPE_ACTIVE, remote_ip, all_fingerprint_extra, self)
|
||||
|
||||
# record the alert
|
||||
fraud = FraudAlert.create(mf, current_user) if mf.valid?
|
||||
fraud_admin_url = fraud.admin_url if fraud
|
||||
|
||||
|
||||
AdminMailer.alerts(subject: "'All' fingerprint collision by #{current_user.name}",
|
||||
body: "Current User: #{current_user.admin_url}\n\n Fraud Alert: #{fraud_admin_url}").deliver
|
||||
|
||||
# try to record the other fingerprint
|
||||
mf = MachineFingerprint.create(running_fingerprint, current_user, MachineFingerprint::TAKEN_ON_FRAUD_CONFLICT, MachineFingerprint::PRINT_TYPE_ACTIVE, remote_ip, running_fingerprint_extra, self)
|
||||
|
||||
if APP_CONFIG.error_on_fraud
|
||||
return "other user has 'all' fingerprint"
|
||||
else
|
||||
self.redeemed_and_fingerprinted = true
|
||||
save!
|
||||
return nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
if all_fingerprint != running_fingerprint
|
||||
if FingerprintWhitelist.select('id').find_by_fingerprint(running_fingerprint)
|
||||
# we can short circuit out of the rest of the check, since this is a known bad fingerprint
|
||||
@@log.debug("ignoring 'running' hash found in whitelist")
|
||||
else
|
||||
|
||||
conflict = MachineFingerprint.select('count(id) as count').where('user_id != ?', current_user.id).where(fingerprint: running_fingerprint).where(remote_ip: remote_ip).where('created_at > ?', APP_CONFIG.expire_fingerprint_days.days.ago).first
|
||||
conflict_count = conflict['count'].to_i
|
||||
if conflict_count >= APP_CONFIG.found_conflict_count
|
||||
mf = MachineFingerprint.create(running_fingerprint, current_user, MachineFingerprint::TAKEN_ON_FRAUD_CONFLICT, MachineFingerprint::PRINT_TYPE_ACTIVE, remote_ip, running_fingerprint_extra, self)
|
||||
|
||||
# record the alert
|
||||
fraud = FraudAlert.create(mf, current_user) if mf.valid?
|
||||
fraud_admin_url = fraud.admin_url if fraud
|
||||
AdminMailer.alerts(subject: "'Running' fingerprint collision by #{current_user.name}",
|
||||
body: "Current User: #{current_user.admin_url}\n\nFraud Alert: #{fraud_admin_url}").deliver\
|
||||
|
||||
# try to record the other fingerprint
|
||||
mf = MachineFingerprint.create(all_fingerprint, current_user, MachineFingerprint::TAKEN_ON_FRAUD_CONFLICT, MachineFingerprint::PRINT_TYPE_ALL, remote_ip, all_fingerprint_extra, self)
|
||||
|
||||
|
||||
if APP_CONFIG.error_on_fraud
|
||||
return "other user has 'running' fingerprint"
|
||||
else
|
||||
self.redeemed_and_fingerprinted = true
|
||||
save!
|
||||
return nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# we made it past all checks; let's slap on the redeemed_fingerprint
|
||||
self.redeemed_and_fingerprinted = true
|
||||
|
||||
MachineFingerprint.create(all_fingerprint, current_user, MachineFingerprint::TAKEN_ON_SUCCESSFUL_DOWNLOAD, MachineFingerprint::PRINT_TYPE_ALL, remote_ip, all_fingerprint_extra, self)
|
||||
if all_fingerprint != running_fingerprint
|
||||
MachineFingerprint.create(running_fingerprint, current_user, MachineFingerprint::TAKEN_ON_SUCCESSFUL_DOWNLOAD, MachineFingerprint::PRINT_TYPE_ACTIVE, remote_ip, running_fingerprint_extra, self)
|
||||
end
|
||||
|
||||
save!
|
||||
end
|
||||
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
def self.stats
|
||||
stats = {}
|
||||
|
||||
result = JamTrackRight.select('count(id) as total, count(CASE WHEN signing_44 THEN 1 ELSE NULL END) + count(CASE WHEN signing_48 THEN 1 ELSE NULL END) as signing_count, count(CASE WHEN redeemed THEN 1 ELSE NULL END) as redeem_count, count(last_downloaded_at) as redeemed_and_dl_count').where(is_test_purchase: false).first
|
||||
|
||||
stats['count'] = result['total'].to_i
|
||||
stats['signing_count'] = result['signing_count'].to_i
|
||||
stats['redeemed_count'] = result['redeem_count'].to_i
|
||||
stats['redeemed_and_dl_count'] = result['redeemed_and_dl_count'].to_i
|
||||
stats['purchased_count'] = stats['count'] - stats['redeemed_count']
|
||||
stats
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ module JamRuby
|
|||
|
||||
@@log = Logging.logger[JamTrackTrack]
|
||||
|
||||
before_destroy :delete_s3_files
|
||||
|
||||
# Because JamTrackImporter imports audio files now, and because also the mere presence of this causes serious issues when updating the model (because reset of url_44 to something bogus), I've removed these
|
||||
#mount_uploader :url_48, JamTrackTrackUploader
|
||||
|
|
@ -18,7 +19,9 @@ module JamRuby
|
|||
attr_accessible :jam_track_id, :track_type, :instrument, :instrument_id, :position, :part, as: :admin
|
||||
attr_accessible :url_44, :url_48, :md5_44, :md5_48, :length_44, :length_48, :preview_start_time_raw, as: :admin
|
||||
|
||||
attr_accessor :original_audio_s3_path, :skip_uploader
|
||||
attr_accessor :original_audio_s3_path, :skip_uploader, :preview_generate_error
|
||||
|
||||
before_destroy :delete_s3_files
|
||||
|
||||
validates :position, presence: true, numericality: {only_integer: true}, length: {in: 1..1000}
|
||||
validates :part, length: {maximum: 25}
|
||||
|
|
@ -57,7 +60,7 @@ module JamRuby
|
|||
def preview_public_url(media_type='ogg')
|
||||
url = media_type == 'ogg' ? self[:preview_url] : self[:preview_mp3_url]
|
||||
if url
|
||||
s3_public_manager.public_url(url,{ :secure => false})
|
||||
s3_public_manager.public_url(url,{ :secure => true})
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
|
@ -84,7 +87,7 @@ module JamRuby
|
|||
# we would verify their rights (can_download?), and generates a URL in response to the click so that they can download
|
||||
# but the url is short lived enough so that it wouldn't be easily shared
|
||||
def sign_url(expiration_time = 120, sample_rate=48)
|
||||
s3_manager.sign_url(url_by_sample_rate(sample_rate), {:expires => expiration_time, :response_content_type => 'audio/ogg', :secure => false})
|
||||
s3_manager.sign_url(url_by_sample_rate(sample_rate), {:expires => expiration_time, :response_content_type => 'audio/ogg', :secure => true})
|
||||
end
|
||||
|
||||
def can_download?(user)
|
||||
|
|
@ -120,7 +123,99 @@ module JamRuby
|
|||
end
|
||||
end
|
||||
|
||||
private
|
||||
def delete_s3_files
|
||||
s3_manager.delete(self[:url_44]) if self[:url_44] && s3_manager.exists?(self[:url_44])
|
||||
s3_manager.delete(self[:url_48]) if self[:url_48] && s3_manager.exists?(self[:url_48])
|
||||
s3_public_manager.delete(self[:preview_url]) if self[:preview_url] && s3_public_manager.exists?(self[:preview_url])
|
||||
s3_public_manager.delete(self[:preview_mp3_url]) if self[:preview_mp3_url] && s3_public_manager.exists?(self[:preview_mp3_url])
|
||||
end
|
||||
|
||||
|
||||
def generate_preview
|
||||
|
||||
begin
|
||||
Dir.mktmpdir do |tmp_dir|
|
||||
|
||||
input = File.join(tmp_dir, 'in.ogg')
|
||||
output = File.join(tmp_dir, 'out.ogg')
|
||||
output_mp3 = File.join(tmp_dir, 'out.mp3')
|
||||
|
||||
start = self.preview_start_time.to_f / 1000
|
||||
stop = start + 20
|
||||
|
||||
raise 'no track' unless self["url_44"]
|
||||
|
||||
s3_manager.download(self.url_by_sample_rate(44), input)
|
||||
|
||||
command = "sox \"#{input}\" \"#{output}\" trim #{sprintf("%.3f", start)} =#{sprintf("%.3f", stop)}"
|
||||
|
||||
@@log.debug("trimming using: " + command)
|
||||
|
||||
sox_output = `#{command}`
|
||||
|
||||
result_code = $?.to_i
|
||||
|
||||
if result_code != 0
|
||||
@@log.debug("fail #{result_code}")
|
||||
@preview_generate_error = "unable to execute cut command #{sox_output}"
|
||||
else
|
||||
# now create mp3 off of ogg preview
|
||||
|
||||
convert_mp3_cmd = "#{APP_CONFIG.ffmpeg_path} -i \"#{output}\" -ab 192k \"#{output_mp3}\""
|
||||
|
||||
@@log.debug("converting to mp3 using: " + convert_mp3_cmd)
|
||||
|
||||
convert_output = `#{convert_mp3_cmd}`
|
||||
|
||||
result_code = $?.to_i
|
||||
|
||||
if result_code != 0
|
||||
@@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)
|
||||
|
||||
self.skip_uploader = true
|
||||
|
||||
original_ogg_preview_url = self["preview_url"]
|
||||
original_mp3_preview_url = self["preview_mp3_url"]
|
||||
|
||||
# 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 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"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue Exception => e
|
||||
@@log.error("error in sox command #{e.to_s}")
|
||||
@preview_generate_error = e.to_s
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
def normalize_position
|
||||
parent = self.jam_track
|
||||
position = 0
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
module JamRuby
|
||||
class MachineExtra < ActiveRecord::Base
|
||||
|
||||
@@log = Logging.logger[MachineExtra]
|
||||
|
||||
belongs_to :machine_fingerprint, :class_name => "JamRuby::MachineFingerprint"
|
||||
|
||||
def self.create(machine_fingerprint, data)
|
||||
me = MachineExtra.new
|
||||
me.machine_fingerprint = machine_fingerprint
|
||||
me.mac_address = data[:mac]
|
||||
me.mac_name = data[:name]
|
||||
me.upstate = data[:upstate]
|
||||
me.ipaddr_0 = data[:ipaddr_0]
|
||||
me.ipaddr_1 = data[:ipaddr_1]
|
||||
me.ipaddr_2 = data[:ipaddr_2]
|
||||
me.ipaddr_3 = data[:ipaddr_3]
|
||||
me.ipaddr_4 = data[:ipaddr_4]
|
||||
me.ipaddr_5 = data[:ipaddr_5]
|
||||
me.save
|
||||
|
||||
unless me.save
|
||||
@@log.error("unable to create machine extra: #{me.errors.inspect}")
|
||||
end
|
||||
end
|
||||
|
||||
def admin_url
|
||||
APP_CONFIG.admin_root_url + "/admin/machine_extras/" + id
|
||||
end
|
||||
|
||||
def to_s
|
||||
"#{mac_address} #{mac_name} #{upstate ? 'UP' : 'DOWN'} #{ipaddr_0} #{ipaddr_1} #{ipaddr_2} #{ipaddr_3} #{ipaddr_4} #{ipaddr_5}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
module JamRuby
|
||||
class MachineFingerprint < ActiveRecord::Base
|
||||
|
||||
@@log = Logging.logger[MachineFingerprint]
|
||||
|
||||
belongs_to :user, :class_name => "JamRuby::User"
|
||||
belongs_to :jam_track_right, :class_name => "JamRuby::JamTrackRight"
|
||||
has_one :detail, :class_name => "JamRuby::MachineExtra"
|
||||
belongs_to :fingerprint_whitelist, class_name: 'JamRuby::FingerprintWhitelist', foreign_key: :fingerprint
|
||||
|
||||
TAKEN_ON_SUCCESSFUL_DOWNLOAD = 'dl'
|
||||
TAKEN_ON_FRAUD_CONFLICT = 'fc'
|
||||
|
||||
PRINT_TYPE_ALL = 'a'
|
||||
PRINT_TYPE_ACTIVE = 'r'
|
||||
|
||||
|
||||
validates :user, presence:true
|
||||
validates :when_taken, :inclusion => {:in => [TAKEN_ON_SUCCESSFUL_DOWNLOAD, TAKEN_ON_FRAUD_CONFLICT]}
|
||||
validates :fingerprint, presence: true
|
||||
validates :print_type, presence: true, :inclusion => {:in =>[PRINT_TYPE_ALL, PRINT_TYPE_ACTIVE]}
|
||||
validates :remote_ip, presence: true
|
||||
|
||||
def self.create(fingerprint, user, when_taken, print_type, remote_ip, extra, jam_track_right = nil)
|
||||
mf = MachineFingerprint.new
|
||||
mf.fingerprint = fingerprint
|
||||
mf.user = user
|
||||
mf.when_taken = when_taken
|
||||
mf.print_type = print_type
|
||||
mf.remote_ip = remote_ip
|
||||
mf.jam_track_right = jam_track_right
|
||||
if mf.save
|
||||
MachineExtra.create(mf, extra) if extra
|
||||
else
|
||||
|
||||
@@log.error("unable to create machine fingerprint: #{mf.errors.inspect}")
|
||||
end
|
||||
mf
|
||||
end
|
||||
|
||||
def admin_url
|
||||
APP_CONFIG.admin_root_url + "/admin/machine_fingerprints/" + id
|
||||
end
|
||||
|
||||
def to_s
|
||||
"#{fingerprint} #{remote_ip} #{user} #{detail}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -131,9 +131,10 @@ module JamRuby
|
|||
end
|
||||
end
|
||||
|
||||
uri = URI(sign_url(field))
|
||||
url = sign_url(field)
|
||||
uri = URI(url)
|
||||
open downloaded_filename, 'wb' do |io|
|
||||
Net::HTTP.start(uri.host, uri.port) do |http|
|
||||
Net::HTTP.start(uri.host, uri.port, use_ssl: url.start_with?('https') ? true : false) do |http|
|
||||
request = Net::HTTP::Get.new uri
|
||||
http.request request do |response|
|
||||
response_code = response.code.to_i
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ module JamRuby
|
|||
|
||||
MAX_MIX_TIME = 7200 # 2 hours
|
||||
|
||||
@@log = Logging.logger[Mix]
|
||||
|
||||
before_destroy :delete_s3_files
|
||||
|
||||
self.primary_key = 'id'
|
||||
|
|
@ -137,15 +139,67 @@ module JamRuby
|
|||
one_day = 60 * 60 * 24
|
||||
|
||||
jam_track_offset = 0
|
||||
jam_track_seek = 0
|
||||
|
||||
was_jamtrack_played = false
|
||||
|
||||
if recording.timeline
|
||||
recording_timeline_data = JSON.parse(recording.timeline)
|
||||
|
||||
# did the jam track play at all?
|
||||
jam_track_isplaying = recording_timeline_data["jam_track_isplaying"]
|
||||
recording_start_time = recording_timeline_data["recording_start_time"]
|
||||
jam_track_play_start_time = recording_timeline_data["jam_track_play_start_time"]
|
||||
jam_track_recording_start_play_offset = recording_timeline_data["jam_track_recording_start_play_offset"]
|
||||
|
||||
jam_track_offset = -jam_track_recording_start_play_offset
|
||||
if jam_track_play_start_time != 0
|
||||
was_jamtrack_played = true
|
||||
|
||||
# how long did the JamTrack play? not needed because we limit on the input tracks, which represents how long the recording is, too
|
||||
jam_track_play_time = recording_timeline_data["jam_track_play_time"]
|
||||
|
||||
|
||||
offset = jam_track_play_start_time - recording_start_time
|
||||
|
||||
@@log.debug("base offset = #{offset}")
|
||||
if offset >= 0
|
||||
# jamtrack started after recording, so buffer with silence as necessary\
|
||||
|
||||
if jam_track_recording_start_play_offset < 0
|
||||
@@log.info("prelude captured. offsetting further by #{-jam_track_recording_start_play_offset}")
|
||||
# a negative jam_track_recording_start_play_offset indicates prelude, i.e., silence
|
||||
# so add it to the offset to add more silence as necessary
|
||||
offset = offset + -jam_track_recording_start_play_offset
|
||||
jam_track_offset = offset
|
||||
else
|
||||
@@log.info("positive jamtrack offset; seeking into jamtrack by #{jam_track_recording_start_play_offset}")
|
||||
# a positive jam_track_recording_start_play_offset means we need to cut into the jamtrack
|
||||
jam_track_seek = jam_track_recording_start_play_offset
|
||||
jam_track_offset = offset
|
||||
end
|
||||
else
|
||||
# jamtrack started before recording, so we can seek into it to make up for the missing parts
|
||||
|
||||
if jam_track_recording_start_play_offset < 0
|
||||
@@log.info("partial prelude captured. offset becomes jamtrack offset#{-jam_track_recording_start_play_offset}")
|
||||
# a negative jam_track_recording_start_play_offset indicates prelude, i.e., silence
|
||||
# so add it to the offset to add more silence as necessary
|
||||
jam_track_offset = -jam_track_recording_start_play_offset
|
||||
else
|
||||
@@log.info("no prelude captured. offset becomes jamtrack offset=#{jam_track_recording_start_play_offset}")
|
||||
|
||||
jam_track_offset = 0
|
||||
jam_track_seek = jam_track_recording_start_play_offset
|
||||
end
|
||||
|
||||
|
||||
# also, ignore jam_track_recording_start_play_offset - it simply matches the offset in this case
|
||||
end
|
||||
|
||||
@@log.info("computed values. jam_track_offset=#{jam_track_offset} jam_track_seek=#{jam_track_seek}")
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
manifest = { "files" => [], "timeline" => [] }
|
||||
|
|
@ -154,7 +208,7 @@ module JamRuby
|
|||
|
||||
# this 'pick limiter' logic will ensure that we set a limiter on the 1st recorded_track we come across.
|
||||
pick_limiter = false
|
||||
if recording.is_jamtrack_recording?
|
||||
if was_jamtrack_played
|
||||
# we only use the limiter feature if this is a JamTrack recording
|
||||
# by setting this to true, the 1st recorded_track in the database will be the limiter
|
||||
pick_limiter = true
|
||||
|
|
@ -171,27 +225,29 @@ module JamRuby
|
|||
mix_params << { "level" => 1.0, "balance" => 0 }
|
||||
end
|
||||
|
||||
recording.recorded_jam_track_tracks.each do |recorded_jam_track_track|
|
||||
manifest["files"] << { "filename" => recorded_jam_track_track.jam_track_track.sign_url(one_day), "codec" => "vorbis", "offset" => jam_track_offset }
|
||||
# let's look for level info from the client
|
||||
level = 1.0 # default value - means no effect
|
||||
if recorded_jam_track_track.timeline
|
||||
if was_jamtrack_played
|
||||
recording.recorded_jam_track_tracks.each do |recorded_jam_track_track|
|
||||
manifest["files"] << { "filename" => recorded_jam_track_track.jam_track_track.sign_url(one_day, sample_rate=44), "codec" => "vorbis", "offset" => jam_track_offset, "seek" => jam_track_seek }
|
||||
# let's look for level info from the client
|
||||
level = 1.0 # default value - means no effect
|
||||
if recorded_jam_track_track.timeline
|
||||
|
||||
timeline_data = JSON.parse(recorded_jam_track_track.timeline)
|
||||
timeline_data = JSON.parse(recorded_jam_track_track.timeline)
|
||||
|
||||
# always take the 1st entry for now
|
||||
first = timeline_data[0]
|
||||
# always take the 1st entry for now
|
||||
first = timeline_data[0]
|
||||
|
||||
if first["mute"]
|
||||
# mute equates to no noise
|
||||
level = 0.0
|
||||
else
|
||||
# otherwise grab the left channel...
|
||||
level = first["vol_l"]
|
||||
if first["mute"]
|
||||
# mute equates to no noise
|
||||
level = 0.0
|
||||
else
|
||||
# otherwise grab the left channel...
|
||||
level = first["vol_l"]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
mix_params << { "level" => level, "balance" => 0 }
|
||||
mix_params << { "level" => level, "balance" => 0 }
|
||||
end
|
||||
end
|
||||
|
||||
manifest["timeline"] << { "timestamp" => 0, "mix" => mix_params }
|
||||
|
|
@ -200,14 +256,47 @@ module JamRuby
|
|||
manifest
|
||||
end
|
||||
|
||||
|
||||
def local_manifest
|
||||
remote_manifest = self.manifest
|
||||
remote_manifest["files"].each do |file|
|
||||
filename = file["filename"]
|
||||
|
||||
basename = File.basename(filename)
|
||||
basename = basename[0..(basename.index('?') - 1)]
|
||||
|
||||
file["filename"] = basename
|
||||
end
|
||||
|
||||
# update manifest so that audiomixer writes here
|
||||
remote_manifest["output"]["filename"] = 'out.ogg'
|
||||
# update manifest so that audiomixer writes here
|
||||
remote_manifest["error_out"] = 'error.out'
|
||||
remote_manifest["mix_id"] = self.id
|
||||
remote_manifest
|
||||
end
|
||||
|
||||
def download_script
|
||||
out = ''
|
||||
|
||||
remote_manifest = manifest
|
||||
remote_manifest["files"].each do |file|
|
||||
filename = file["filename"]
|
||||
basename = File.basename(filename)
|
||||
basename = basename[0..(basename.index('?') - 1)]
|
||||
|
||||
out << "curl -o \"#{basename}\" \"#{filename}\"\r\n\r\n"
|
||||
end
|
||||
out << "\r\n\r\n"
|
||||
out
|
||||
end
|
||||
|
||||
def s3_url(type='ogg')
|
||||
if type == 'ogg'
|
||||
s3_manager.s3_url(self[:ogg_url])
|
||||
else
|
||||
s3_manager.s3_url(self[:mp3_url])
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
def is_completed
|
||||
|
|
@ -216,7 +305,7 @@ module JamRuby
|
|||
|
||||
# if the url starts with http, just return it because it's in some other store. Otherwise it's a relative path in s3 and needs be signed
|
||||
def resolve_url(url_field, mime_type, expiration_time)
|
||||
self[url_field].start_with?('http') ? self[url_field] : s3_manager.sign_url(self[url_field], {:expires => expiration_time, :response_content_type => mime_type, :secure => false})
|
||||
self[url_field].start_with?('http') ? self[url_field] : s3_manager.sign_url(self[url_field], {:expires => expiration_time, :response_content_type => mime_type, :secure => true})
|
||||
end
|
||||
|
||||
def sign_url(expiration_time = 120, type='ogg')
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ module JamRuby
|
|||
end
|
||||
|
||||
def sign_url(expiration_time = 120)
|
||||
s3_manager.sign_url(self[:file_url], {:expires => expiration_time, :secure => false})
|
||||
s3_manager.sign_url(self[:file_url], {:expires => expiration_time, :secure => true})
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
|||
|
|
@ -282,7 +282,7 @@ module JamRuby
|
|||
return query
|
||||
end
|
||||
|
||||
def self.scheduled user
|
||||
def self.scheduled user, only_public = false
|
||||
# keep unstarted sessions around for 12 hours after scheduled_start
|
||||
session_not_started = "(music_sessions.scheduled_start > NOW() - '12 hour'::INTERVAL AND music_sessions.started_at IS NULL)"
|
||||
|
||||
|
|
@ -293,6 +293,7 @@ module JamRuby
|
|||
session_finished = "(music_sessions.session_removed_at > NOW() - '2 hour'::INTERVAL)"
|
||||
|
||||
query = MusicSession.where("music_sessions.canceled = FALSE")
|
||||
query = query.where('music_sessions.fan_access = TRUE or music_sessions.musician_access = TRUE') if only_public
|
||||
query = query.where("music_sessions.user_id = '#{user.id}'")
|
||||
query = query.where("music_sessions.scheduled_start IS NULL OR #{session_not_started} OR #{session_finished} OR #{session_started_not_finished}")
|
||||
query = query.where("music_sessions.create_type IS NULL OR music_sessions.create_type != '#{CREATE_TYPE_QUICK_START}'")
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ module JamRuby
|
|||
session_user_history.music_session_id = music_session_id
|
||||
session_user_history.user_id = user_id
|
||||
session_user_history.client_id = client_id
|
||||
session_user_history.instruments = tracks.map {|t| t[:instrument_id]}.join("|")
|
||||
session_user_history.instruments = tracks.map {|t| t[:instrument_id]}.join("|") if tracks
|
||||
session_user_history.save
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -210,7 +210,7 @@ module JamRuby
|
|||
return "New message about session."
|
||||
|
||||
when NotificationTypes::JAM_TRACK_SIGN_COMPLETE
|
||||
return "Jam Track is ready for download."
|
||||
return "JamTrack is ready for download."
|
||||
|
||||
# recording notifications
|
||||
when NotificationTypes::MUSICIAN_RECORDING_SAVED
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
module JamRuby
|
||||
class PaymentHistory < ActiveRecord::Base
|
||||
|
||||
self.table_name = 'payment_histories'
|
||||
|
||||
belongs_to :sale
|
||||
belongs_to :recurly_transaction_web_hook
|
||||
|
||||
|
||||
def self.index(user, params = {})
|
||||
|
||||
limit = params[:per_page]
|
||||
limit ||= 20
|
||||
limit = limit.to_i
|
||||
|
||||
query = PaymentHistory.limit(limit)
|
||||
.includes(sale: [:sale_line_items], recurly_transaction_web_hook:[])
|
||||
.where(user_id: user.id)
|
||||
.where("transaction_type = 'sale' OR transaction_type = 'refund' OR transaction_type = 'void'")
|
||||
.order('created_at DESC')
|
||||
|
||||
|
||||
current_page = params[:page].nil? ? 1 : params[:page].to_i
|
||||
next_page = current_page + 1
|
||||
|
||||
# will_paginate gem
|
||||
query = query.paginate(:page => current_page, :per_page => limit)
|
||||
|
||||
if query.length == 0 # no more results
|
||||
{ query: query, next_page: nil}
|
||||
elsif query.length < limit # no more results
|
||||
{ query: query, next_page: nil}
|
||||
else
|
||||
{ query: query, next_page: next_page }
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -219,7 +219,7 @@ module JamRuby
|
|||
|
||||
# if the url starts with http, just return it because it's in some other store. Otherwise it's a relative path in s3 and needs be signed
|
||||
def resolve_url(url_field, mime_type, expiration_time)
|
||||
self[url_field].start_with?('http') ? self[url_field] : s3_manager.sign_url(self[url_field], {:expires => expiration_time, :response_content_type => mime_type, :secure => false})
|
||||
self[url_field].start_with?('http') ? self[url_field] : s3_manager.sign_url(self[url_field], {:expires => expiration_time, :response_content_type => mime_type, :secure => true})
|
||||
end
|
||||
|
||||
def sign_url(expiration_time = 120, type='ogg')
|
||||
|
|
@ -232,6 +232,7 @@ module JamRuby
|
|||
end
|
||||
end
|
||||
|
||||
# this is not 'secure' because, in testing, the PUT failed often in Ruby. should investigate more.
|
||||
def sign_put(expiration_time = 3600 * 24, type='ogg')
|
||||
type ||= 'ogg'
|
||||
if type == 'ogg'
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ module JamRuby
|
|||
end
|
||||
|
||||
def sign_url(expiration_time = 120)
|
||||
s3_manager.sign_url(self[:url], {:expires => expiration_time, :response_content_type => 'audio/ogg', :secure => false})
|
||||
s3_manager.sign_url(self[:url], {:expires => expiration_time, :response_content_type => 'audio/ogg', :secure => true})
|
||||
end
|
||||
|
||||
def can_download?(some_user)
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ module JamRuby
|
|||
end
|
||||
|
||||
def sign_url(expiration_time = 120)
|
||||
s3_manager.sign_url(self[:url], {:expires => expiration_time, :response_content_type => 'audio/ogg', :secure => false})
|
||||
s3_manager.sign_url(self[:url], {:expires => expiration_time, :response_content_type => 'audio/ogg', :secure => true})
|
||||
end
|
||||
|
||||
def upload_start(length, md5)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ module JamRuby
|
|||
|
||||
@@log = Logging.logger[Recording]
|
||||
|
||||
attr_accessible :owner, :owner_id, :band, :band_id, :recorded_tracks_attributes, :mixes_attributes, :claimed_recordings_attributes, :name, :description, :genre, :is_public, :duration, as: :admin
|
||||
attr_accessible :owner, :owner_id, :band, :band_id, :recorded_tracks_attributes, :mixes_attributes, :claimed_recordings_attributes, :name, :description, :genre, :is_public, :duration, :jam_track_id, as: :admin
|
||||
|
||||
has_many :users, :through => :recorded_tracks, :class_name => "JamRuby::User"
|
||||
has_many :claimed_recordings, :class_name => "JamRuby::ClaimedRecording", :inverse_of => :recording, :foreign_key => 'recording_id', :dependent => :destroy
|
||||
|
|
@ -21,6 +21,7 @@ module JamRuby
|
|||
belongs_to :owner, :class_name => "JamRuby::User", :inverse_of => :owned_recordings, :foreign_key => 'owner_id'
|
||||
belongs_to :band, :class_name => "JamRuby::Band", :inverse_of => :recordings
|
||||
belongs_to :music_session, :class_name => "JamRuby::ActiveMusicSession", :inverse_of => :recordings, foreign_key: :music_session_id
|
||||
belongs_to :non_active_music_session, :class_name => "JamRuby::MusicSession", foreign_key: :music_session_id
|
||||
belongs_to :jam_track, :class_name => "JamRuby::JamTrack", :inverse_of => :recordings, :foreign_key => 'jam_track_id'
|
||||
belongs_to :jam_track_initiator, :class_name => "JamRuby::User", :inverse_of => :initiated_jam_track_recordings, :foreign_key => 'jam_track_initiator_id'
|
||||
|
||||
|
|
@ -50,7 +51,11 @@ module JamRuby
|
|||
end
|
||||
|
||||
def is_jamtrack_recording?
|
||||
!jam_track_id.nil?
|
||||
!jam_track_id.nil? && parsed_timeline['jam_track_isplaying']
|
||||
end
|
||||
|
||||
def parsed_timeline
|
||||
timeline ? JSON.parse(timeline) : {}
|
||||
end
|
||||
|
||||
def high_quality_mix?
|
||||
|
|
@ -182,21 +187,21 @@ module JamRuby
|
|||
|
||||
def recorded_tracks_for_user(user)
|
||||
unless self.users.exists?(user)
|
||||
raise PermissionError, "user was not in this session"
|
||||
raise JamPermissionError, "user was not in this session"
|
||||
end
|
||||
recorded_tracks.where(:user_id => user.id)
|
||||
end
|
||||
|
||||
def recorded_backing_tracks_for_user(user)
|
||||
unless self.users.exists?(user)
|
||||
raise PermissionError, "user was not in this session"
|
||||
raise JamPermissionError, "user was not in this session"
|
||||
end
|
||||
recorded_backing_tracks.where(:user_id => user.id)
|
||||
end
|
||||
|
||||
|
||||
def has_access?(user)
|
||||
users.exists?(user)
|
||||
users.exists?(user) || plays.where("player_id=?", user).count != 0
|
||||
end
|
||||
|
||||
# Start recording a session.
|
||||
|
|
@ -264,7 +269,7 @@ module JamRuby
|
|||
def claim(user, name, description, genre, is_public, upload_to_youtube=false)
|
||||
upload_to_youtube = !!upload_to_youtube # Correct where nil is borking save
|
||||
unless self.users.exists?(user)
|
||||
raise PermissionError, "user was not in this session"
|
||||
raise JamPermissionError, "user was not in this session"
|
||||
end
|
||||
|
||||
claimed_recording = ClaimedRecording.new
|
||||
|
|
@ -710,23 +715,26 @@ module JamRuby
|
|||
end
|
||||
end
|
||||
|
||||
def self.popular_recordings(limit = 100)
|
||||
Recording.select('recordings.id').joins('inner join claimed_recordings ON claimed_recordings.recording_id = recordings.id AND claimed_recordings.is_public = TRUE').where(all_discarded: false).where(is_done: true).where(deleted: false).order('play_count DESC').limit(limit).group('recordings.id')
|
||||
end
|
||||
|
||||
private
|
||||
def self.validate_user_is_band_member(user, band)
|
||||
unless band.users.exists? user
|
||||
raise PermissionError, ValidationMessages::USER_NOT_BAND_MEMBER_VALIDATION_ERROR
|
||||
raise JamPermissionError, ValidationMessages::USER_NOT_BAND_MEMBER_VALIDATION_ERROR
|
||||
end
|
||||
end
|
||||
|
||||
def self.validate_user_is_creator(user, creator)
|
||||
unless user.id == creator.id
|
||||
raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR
|
||||
raise JamPermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR
|
||||
end
|
||||
end
|
||||
|
||||
def self.validate_user_is_musician(user)
|
||||
unless user.musician?
|
||||
raise PermissionError, ValidationMessages::USER_NOT_MUSICIAN_VALIDATION_ERROR
|
||||
raise JamPermissionError, ValidationMessages::USER_NOT_MUSICIAN_VALIDATION_ERROR
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,20 +1,42 @@
|
|||
module JamRuby
|
||||
class RecurlyTransactionWebHook < ActiveRecord::Base
|
||||
class RecurlyTransactionWebHook < ActiveRecord::Base
|
||||
|
||||
attr_accessible :admin_description, :jam_track_id, as: :admin
|
||||
|
||||
belongs_to :user, class_name: 'JamRuby::User'
|
||||
belongs_to :sale_line_item, class_name: 'JamRuby::SaleLineItem', foreign_key: 'subscription_id', primary_key: 'recurly_subscription_uuid', inverse_of: :recurly_transactions
|
||||
belongs_to :sale, class_name: 'JamRuby::Sale', foreign_key: 'invoice_id', primary_key: 'recurly_invoice_id', inverse_of: :recurly_transactions
|
||||
|
||||
# when we know what JamTrack this refund is related to, we set this value
|
||||
belongs_to :jam_track, class_name: 'JamRuby::JamTrack'
|
||||
|
||||
validates :recurly_transaction_id, presence: true
|
||||
validates :subscription_id, presence: true
|
||||
validates :action, presence: true
|
||||
validates :status, presence: true
|
||||
validates :amount_in_cents, numericality: {only_integer: true}
|
||||
validates :user, presence: true
|
||||
|
||||
|
||||
SUCCESSFUL_PAYMENT = 'payment'
|
||||
FAILED_PAYMENT = 'failed_payment'
|
||||
REFUND = 'refund'
|
||||
VOID = 'void'
|
||||
|
||||
|
||||
HOOK_TYPES = [SUCCESSFUL_PAYMENT, FAILED_PAYMENT, REFUND, VOID]
|
||||
|
||||
def is_credit_type?
|
||||
transaction_type == REFUND || transaction_type == VOID
|
||||
end
|
||||
|
||||
def is_voided?
|
||||
transaction_type == VOID
|
||||
end
|
||||
|
||||
def is_refund?
|
||||
transaction_type == REFUND
|
||||
end
|
||||
|
||||
def self.is_transaction_web_hook?(document)
|
||||
|
||||
return false if document.root.nil?
|
||||
|
|
@ -32,6 +54,10 @@ module JamRuby
|
|||
end
|
||||
end
|
||||
|
||||
def admin_url
|
||||
APP_CONFIG.admin_root_url + "/admin/recurly_hooks/" + id
|
||||
end
|
||||
|
||||
# see spec for examples of XML
|
||||
def self.create_from_xml(document)
|
||||
|
||||
|
|
@ -67,9 +93,60 @@ module JamRuby
|
|||
|
||||
# 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'
|
||||
right = JamTrackRight.find_by_recurly_subscription_uuid(transaction.subscription_id)
|
||||
right.destroy if right
|
||||
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!
|
||||
|
||||
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",
|
||||
body: "You will have to manually revoke any JamTrackRights in our database for the appropriate JamTracks"
|
||||
}).deliver
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
transaction
|
||||
end
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ module JamRuby
|
|||
invitation = Invitation.where("music_session_id = ? AND receiver_id = ?", music_session.id, user.id)
|
||||
|
||||
if invitation.first.nil? && !music_session.open_rsvps && music_session.creator.id != user.id
|
||||
raise PermissionError, "Only a session invitee can create an RSVP for this session."
|
||||
raise JamPermissionError, "Only a session invitee can create an RSVP for this session."
|
||||
end
|
||||
|
||||
RsvpRequest.transaction do
|
||||
|
|
@ -154,7 +154,7 @@ module JamRuby
|
|||
|
||||
# authorize the user attempting to respond to the RSVP request
|
||||
if music_session.creator.id != user.id
|
||||
raise PermissionError, "Only the session organizer can accept or decline an RSVP request."
|
||||
raise JamPermissionError, "Only the session organizer can accept or decline an RSVP request."
|
||||
end
|
||||
|
||||
rsvp_request = RsvpRequest.find_by_id(rsvp_request_id)
|
||||
|
|
@ -249,7 +249,7 @@ module JamRuby
|
|||
rsvp_request = RsvpRequest.find(params[:id])
|
||||
|
||||
if music_session.creator.id != user.id && rsvp_request.user_id != user.id
|
||||
raise PermissionError, "Only the session organizer or RSVP creator can cancel the RSVP."
|
||||
raise JamPermissionError, "Only the session organizer or RSVP creator can cancel the RSVP."
|
||||
end
|
||||
|
||||
RsvpRequest.transaction do
|
||||
|
|
|
|||
|
|
@ -3,30 +3,370 @@ module JamRuby
|
|||
# a sale is created every time someone tries to buy something
|
||||
class Sale < ActiveRecord::Base
|
||||
|
||||
JAMTRACK_SALE = 'jamtrack'
|
||||
|
||||
belongs_to :user, class_name: 'JamRuby::User'
|
||||
has_many :sale_line_items, class_name: 'JamRuby::SaleLineItem'
|
||||
|
||||
validates :order_total, numericality: { only_integer: false }
|
||||
has_many :recurly_transactions, class_name: 'JamRuby::RecurlyTransactionWebHook', inverse_of: :sale, foreign_key: 'invoice_id', primary_key: 'recurly_invoice_id'
|
||||
|
||||
validates :order_total, numericality: {only_integer: false}
|
||||
validates :user, presence: true
|
||||
|
||||
def self.create(user)
|
||||
@@log = Logging.logger[Sale]
|
||||
|
||||
def self.index(user, params = {})
|
||||
|
||||
limit = params[:per_page]
|
||||
limit ||= 20
|
||||
limit = limit.to_i
|
||||
|
||||
query = Sale.limit(limit)
|
||||
.includes([:recurly_transactions, :sale_line_items])
|
||||
.where('sales.user_id' => user.id)
|
||||
.order('sales.created_at DESC')
|
||||
|
||||
current_page = params[:page].nil? ? 1 : params[:page].to_i
|
||||
next_page = current_page + 1
|
||||
|
||||
# will_paginate gem
|
||||
query = query.paginate(:page => current_page, :per_page => limit)
|
||||
|
||||
if query.length == 0 # no more results
|
||||
{ query: query, next_page: nil}
|
||||
elsif query.length < limit # no more results
|
||||
{ query: query, next_page: nil}
|
||||
else
|
||||
{ query: query, next_page: next_page }
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def state
|
||||
original_total = self.recurly_total_in_cents
|
||||
|
||||
is_voided = false
|
||||
refund_total = 0
|
||||
|
||||
recurly_transactions.each do |transaction|
|
||||
if transaction.is_voided?
|
||||
is_voided = true
|
||||
else
|
||||
|
||||
end
|
||||
|
||||
if transaction.is_refund?
|
||||
refund_total = refund_total + transaction.amount_in_cents
|
||||
end
|
||||
end
|
||||
|
||||
# if refund_total is > 0, then you have a refund.
|
||||
# if voided is true, then in theory the whole thing has been refunded
|
||||
{
|
||||
voided: is_voided,
|
||||
original_total: original_total,
|
||||
refund_total: refund_total
|
||||
}
|
||||
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
|
||||
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
|
||||
# individual subscriptions will end up create their own sale (you can't have N subscriptions in one sale--recurly limitation)
|
||||
# jamtracks however can be piled onto the same sale as adjustments (VRFS-3028)
|
||||
# so this method may create 1 or more sales, , where 2 or more sales can occur if there are more than one subscriptions or subscription + jamtrack
|
||||
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
|
||||
end
|
||||
|
||||
jam_track_sale = order_jam_tracks(current_user, shopping_carts_jam_tracks)
|
||||
sales << jam_track_sale if jam_track_sale
|
||||
|
||||
# TODO: process shopping_carts_subscriptions
|
||||
|
||||
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
|
||||
|
||||
# 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
|
||||
end
|
||||
|
||||
def self.is_only_freebie(shopping_carts_jam_tracks)
|
||||
shopping_carts_jam_tracks.length == 1 && shopping_carts_jam_tracks[0].product_info[: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)
|
||||
|
||||
client = RecurlyClient.new
|
||||
|
||||
sale = nil
|
||||
Sale.transaction do
|
||||
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)
|
||||
|
||||
sale.recurly_subtotal_in_cents = 0
|
||||
sale.recurly_tax_in_cents = 0
|
||||
sale.recurly_total_in_cents = 0
|
||||
sale.recurly_currency = 'USD'
|
||||
|
||||
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.save
|
||||
|
||||
else
|
||||
|
||||
account = client.get_account(current_user)
|
||||
if account.present?
|
||||
|
||||
purge_pending_adjustments(account)
|
||||
|
||||
created_adjustments = sale.process_jam_tracks(current_user, shopping_carts_jam_tracks, account)
|
||||
|
||||
# now invoice the sale ... almost done
|
||||
|
||||
begin
|
||||
invoice = account.invoice!
|
||||
sale.recurly_invoice_id = invoice.uuid
|
||||
sale.recurly_invoice_number = invoice.invoice_number
|
||||
|
||||
# now slap in all the real tax/purchase totals
|
||||
sale.recurly_subtotal_in_cents = invoice.subtotal_in_cents
|
||||
sale.recurly_tax_in_cents = invoice.tax_in_cents
|
||||
sale.recurly_total_in_cents = invoice.total_in_cents
|
||||
sale.recurly_currency = invoice.currency
|
||||
|
||||
# and resolve against sale_line_items
|
||||
sale.sale_line_items.each do |sale_line_item|
|
||||
found_line_item = false
|
||||
invoice.line_items.each do |line_item|
|
||||
if line_item.uuid == sale_line_item.recurly_adjustment_uuid
|
||||
sale_line_item.recurly_tax_in_cents = line_item.tax_in_cents
|
||||
sale_line_item.recurly_total_in_cents =line_item.total_in_cents
|
||||
sale_line_item.recurly_currency = line_item.currency
|
||||
sale_line_item.recurly_discount_in_cents = line_item.discount_in_cents
|
||||
found_line_item = true
|
||||
break
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if !found_line_item
|
||||
@@log.error("can't find line item #{sale_line_item.recurly_adjustment_uuid}")
|
||||
puts "CANT FIND LINE ITEM"
|
||||
end
|
||||
end
|
||||
|
||||
unless sale.save
|
||||
raise RecurlyClientError, "Invalid sale (at end)."
|
||||
end
|
||||
rescue Recurly::Resource::Invalid => e
|
||||
# this exception is thrown by invoice! if the invoice is invalid
|
||||
sale.rollback_adjustments(current_user, created_adjustments)
|
||||
sale = nil
|
||||
raise ActiveRecord::Rollback # kill all db activity, but don't break outside logic
|
||||
end
|
||||
else
|
||||
raise RecurlyClientError, "Could not find account to place order."
|
||||
end
|
||||
end
|
||||
else
|
||||
raise RecurlyClientError, "Invalid sale."
|
||||
end
|
||||
end
|
||||
sale
|
||||
end
|
||||
|
||||
def process_jam_tracks(current_user, shopping_carts_jam_tracks, account)
|
||||
|
||||
created_adjustments = []
|
||||
|
||||
begin
|
||||
shopping_carts_jam_tracks.each do |shopping_cart|
|
||||
process_jam_track(current_user, shopping_cart, account, created_adjustments)
|
||||
end
|
||||
rescue Recurly::Error, NoMethodError => x
|
||||
# rollback any adjustments created if error
|
||||
rollback_adjustments(user, created_adjustments)
|
||||
raise RecurlyClientError, x.to_s
|
||||
rescue Exception => e
|
||||
# rollback any adjustments created if error
|
||||
rollback_adjustments(user, created_adjustments)
|
||||
raise e
|
||||
end
|
||||
|
||||
created_adjustments
|
||||
end
|
||||
|
||||
|
||||
def process_jam_track(current_user, shopping_cart, account, created_adjustments)
|
||||
recurly_adjustment_uuid = nil
|
||||
recurly_adjustment_credit_uuid = nil
|
||||
|
||||
# we do this because of ShoppingCart.remove_jam_track_from_cart; if it occurs, which should be rare, we need fresh shopping cart info
|
||||
shopping_cart.reload
|
||||
|
||||
# get the JamTrack in this shopping cart
|
||||
jam_track = 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
|
||||
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)
|
||||
|
||||
adjustments.each do |adjustment|
|
||||
|
||||
# create the adjustment at Recurly (this may not look like it, but it is a REST API)
|
||||
created_adjustment = account.adjustments.new(adjustment)
|
||||
created_adjustment.save
|
||||
|
||||
# if the adjustment could not be made, bail
|
||||
raise RecurlyClientError.new(created_adjustment.errors) if created_adjustment.errors.any?
|
||||
|
||||
# keep track of adjustments we created for this order, in case we have to roll them back
|
||||
created_adjustments << created_adjustment
|
||||
|
||||
if ShoppingCart.is_product_purchase?(adjustment)
|
||||
# this was a normal product adjustment, so track it as such
|
||||
recurly_adjustment_uuid = created_adjustment.uuid
|
||||
else
|
||||
# this was a 'credit' adjustment, so track it as such
|
||||
recurly_adjustment_credit_uuid = created_adjustment.uuid
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# create one sale line item for every jam track
|
||||
sale_line_item = SaleLineItem.create_from_shopping_cart(self, shopping_cart, nil, recurly_adjustment_uuid, recurly_adjustment_credit_uuid)
|
||||
|
||||
# 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}")
|
||||
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
|
||||
|
||||
# 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)
|
||||
end
|
||||
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
|
||||
|
||||
|
||||
def rollback_adjustments(current_user, adjustments)
|
||||
begin
|
||||
adjustments.each { |adjustment| adjustment.destroy }
|
||||
rescue Exception => e
|
||||
AdminMailer.alerts({
|
||||
subject: "ACTION REQUIRED: #{current_user.email} did not have all of his adjustments destroyed in rollback",
|
||||
body: "go delete any adjustments on the account that don't belong. error: #{e}\n\nAdjustments: #{adjustments.inspect}"
|
||||
}).deliver
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
def self.purge_pending_adjustments(account)
|
||||
account.adjustments.pending.find_each do |adjustment|
|
||||
# we only pre-emptively destroy pending adjustments if they appear to be created by the server
|
||||
adjustment.destroy if ShoppingCart.is_server_pending_adjustment?(adjustment)
|
||||
end
|
||||
end
|
||||
|
||||
def is_jam_track_sale?
|
||||
sale_type == JAMTRACK_SALE
|
||||
end
|
||||
|
||||
def self.create_jam_track_sale(user)
|
||||
sale = Sale.new
|
||||
sale.user = user
|
||||
sale.sale_type = JAMTRACK_SALE
|
||||
sale.order_total = 0
|
||||
sale.save
|
||||
sale
|
||||
end
|
||||
|
||||
def self.check_integrity
|
||||
SaleLineItem.select([:total, :not_known, :succeeded, :failed, :refunded, :voided]).find_by_sql(
|
||||
"SELECT COUNT(sale_line_items.id) AS total,
|
||||
COUNT(CASE WHEN transactions.id IS NULL THEN 1 ELSE null END) not_known,
|
||||
COUNT(CASE WHEN transactions.transaction_type = '#{RecurlyTransactionWebHook::SUCCESSFUL_PAYMENT}' THEN 1 ELSE null END) succeeded,
|
||||
COUNT(CASE WHEN transactions.transaction_type = '#{RecurlyTransactionWebHook::FAILED_PAYMENT}' THEN 1 ELSE null END) failed,
|
||||
COUNT(CASE WHEN transactions.transaction_type = '#{RecurlyTransactionWebHook::REFUND}' THEN 1 ELSE null END) refunded,
|
||||
# this checks just jamtrack sales appropriately
|
||||
def self.check_integrity_of_jam_track_sales
|
||||
Sale.select([:total, :voided]).find_by_sql(
|
||||
"SELECT COUNT(sales.id) AS total,
|
||||
COUNT(CASE WHEN transactions.transaction_type = '#{RecurlyTransactionWebHook::VOID}' THEN 1 ELSE null END) voided
|
||||
FROM sale_line_items
|
||||
LEFT OUTER JOIN recurly_transaction_web_hooks as transactions ON subscription_id = recurly_subscription_uuid")
|
||||
FROM sales
|
||||
LEFT OUTER JOIN recurly_transaction_web_hooks as transactions ON invoice_id = sales.recurly_invoice_id
|
||||
WHERE sale_type = '#{JAMTRACK_SALE}'")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,27 +1,70 @@
|
|||
module JamRuby
|
||||
class SaleLineItem < ActiveRecord::Base
|
||||
|
||||
belongs_to :sale, class_name: 'JamRuby::Sale'
|
||||
belongs_to :jam_track, class_name: 'JamRuby::JamTrack'
|
||||
belongs_to :jam_track_right, class_name: 'JamRuby::JamTrackRight'
|
||||
|
||||
JAMBLASTER = 'JamBlaster'
|
||||
JAMCLOUD = 'JamCloud'
|
||||
JAMTRACK = 'JamTrack'
|
||||
|
||||
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 :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 :unit_price, numericality: {only_integer: false}
|
||||
validates :quantity, numericality: {only_integer: true}
|
||||
validates :free, numericality: {only_integer: true}
|
||||
validates :sales_tax, numericality: {only_integer: false}, allow_nil: true
|
||||
validates :shipping_handling, numericality: {only_integer: false}
|
||||
validates :affiliate_referral_fee_in_cents, numericality: {only_integer: false}, allow_nil: true
|
||||
validates :recurly_plan_code, presence:true
|
||||
validates :sale, presence:true
|
||||
|
||||
def self.create_from_shopping_cart(sale, shopping_cart, recurly_subscription_uuid)
|
||||
def product
|
||||
if product_type == JAMTRACK
|
||||
JamTrack.find_by_id(product_id)
|
||||
else
|
||||
raise 'unsupported product type'
|
||||
end
|
||||
end
|
||||
|
||||
def product_info
|
||||
item = product
|
||||
{ name: product.name } if item
|
||||
end
|
||||
|
||||
def state
|
||||
voided = false
|
||||
refunded = false
|
||||
failed = false
|
||||
succeeded = false
|
||||
|
||||
recurly_transactions.each do |transaction|
|
||||
if transaction.transaction_type == RecurlyTransactionWebHook::VOID
|
||||
voided = true
|
||||
elsif transaction.transaction_type == RecurlyTransactionWebHook::REFUND
|
||||
refunded = true
|
||||
elsif transaction.transaction_type == RecurlyTransactionWebHook::FAILED_PAYMENT
|
||||
failed = true
|
||||
elsif transaction.transaction_type == RecurlyTransactionWebHook::SUCCESSFUL_PAYMENT
|
||||
succeeded = true
|
||||
end
|
||||
end
|
||||
|
||||
{
|
||||
void: voided,
|
||||
refund: refunded,
|
||||
fail: failed,
|
||||
success: succeeded
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
def self.create_from_shopping_cart(sale, shopping_cart, recurly_subscription_uuid, recurly_adjustment_uuid, recurly_adjustment_credit_uuid)
|
||||
product_info = shopping_cart.product_info
|
||||
|
||||
sale.order_total = sale.order_total + product_info[:total_price]
|
||||
sale.order_total = sale.order_total + product_info[:real_price]
|
||||
|
||||
sale_line_item = SaleLineItem.new
|
||||
sale_line_item.product_type = shopping_cart.cart_type
|
||||
|
|
@ -33,7 +76,19 @@ module JamRuby
|
|||
sale_line_item.recurly_plan_code = product_info[:plan_code]
|
||||
sale_line_item.product_id = shopping_cart.cart_id
|
||||
sale_line_item.recurly_subscription_uuid = recurly_subscription_uuid
|
||||
sale_line_item.sale = sale
|
||||
sale_line_item.recurly_adjustment_uuid = recurly_adjustment_uuid
|
||||
sale_line_item.recurly_adjustment_credit_uuid = recurly_adjustment_credit_uuid
|
||||
|
||||
# determine if we need to associate this sale with a partner
|
||||
user = shopping_cart.user
|
||||
referral_info = user.should_attribute_sale?(shopping_cart)
|
||||
|
||||
if referral_info
|
||||
sale_line_item.affiliate_referral = user.affiliate_referral
|
||||
sale_line_item.affiliate_referral_fee_in_cents = referral_info[:fee_in_cents]
|
||||
end
|
||||
|
||||
sale.sale_line_items << sale_line_item
|
||||
sale_line_item.save
|
||||
sale_line_item
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,8 +1,19 @@
|
|||
module JamRuby
|
||||
class ShoppingCart < ActiveRecord::Base
|
||||
|
||||
# just a normal purchase; used on the description field of a recurly adjustment
|
||||
PURCHASE_NORMAL = 'purchase-normal'
|
||||
# a free purchase; used on the description field of a recurly adjustment
|
||||
PURCHASE_FREE = 'purchase-free'
|
||||
# a techinicality of Recurly; we create a free-credit adjustment to balance out the free purchase adjustment
|
||||
PURCHASE_FREE_CREDIT = 'purchase-free-credit'
|
||||
|
||||
PURCHASE_REASONS = [PURCHASE_NORMAL, PURCHASE_FREE, PURCHASE_FREE_CREDIT]
|
||||
|
||||
attr_accessible :quantity, :cart_type, :product_info
|
||||
|
||||
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"
|
||||
|
||||
validates :cart_id, presence: true
|
||||
|
|
@ -14,14 +25,20 @@ module JamRuby
|
|||
|
||||
def product_info
|
||||
product = self.cart_product
|
||||
{name: product.name, price: product.price, product_id: cart_id, plan_code: product.plan_code, total_price: total_price(product), quantity: quantity, marked_for_redeem: marked_for_redeem} unless product.nil?
|
||||
{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?
|
||||
end
|
||||
|
||||
# multiply quantity by price
|
||||
def total_price(product)
|
||||
quantity * product.price
|
||||
end
|
||||
|
||||
# multiply (quantity - redeemable) by price
|
||||
def total_price(product)
|
||||
def real_price(product)
|
||||
(quantity - marked_for_redeem) * product.price
|
||||
end
|
||||
|
||||
|
||||
def cart_product
|
||||
self.cart_class_name.classify.constantize.find_by_id(self.cart_id) unless self.cart_class_name.blank?
|
||||
end
|
||||
|
|
@ -51,6 +68,68 @@ module JamRuby
|
|||
cart
|
||||
end
|
||||
|
||||
def is_jam_track?
|
||||
cart_type == JamTrack::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?
|
||||
|
||||
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)",
|
||||
tax_exempt: true
|
||||
},
|
||||
{
|
||||
accounting_code: PURCHASE_FREE,
|
||||
currency: 'USD',
|
||||
unit_amount_in_cents: (info[:total_price] * 100).to_i,
|
||||
description: "JamTrack: " + info[:name],
|
||||
tax_exempt: true
|
||||
}
|
||||
]
|
||||
else
|
||||
[
|
||||
{
|
||||
accounting_code: PURCHASE_NORMAL,
|
||||
currency: 'USD',
|
||||
unit_amount_in_cents: (info[:total_price] * 100).to_i,
|
||||
description: "JamTrack: " + info[:name],
|
||||
tax_exempt: false
|
||||
}
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
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)
|
||||
end
|
||||
|
||||
anonymous_user.destroy_all_shopping_carts
|
||||
end
|
||||
|
||||
def self.is_product_purchase?(adjustment)
|
||||
(adjustment[:accounting_code].include?(PURCHASE_FREE) || adjustment[:accounting_code].include?(PURCHASE_NORMAL)) && !adjustment[:accounting_code].include?(PURCHASE_FREE_CREDIT)
|
||||
end
|
||||
|
||||
# recurly_adjustment is a Recurly::Adjustment (http://www.rubydoc.info/gems/recurly/Recurly/Adjustment)
|
||||
# this asks, 'is this a pending adjustment?' AND 'was this adjustment created by the server (vs manually by someone -- we should leave those alone).'
|
||||
def self.is_server_pending_adjustment?(recurly_adjustment)
|
||||
recurly_adjustment.state == 'pending' && (recurly_adjustment.accounting_code.include?(PURCHASE_FREE) || recurly_adjustment.accounting_code.include?(PURCHASE_NORMAL) || recurly_adjustment.accounting_code.include?(PURCHASE_FREE_CREDIT))
|
||||
end
|
||||
|
||||
# if the user has a redeemable jam_track still on their account, then also check if any shopping carts have already been marked.
|
||||
# if no shpping carts have been marked, then mark it redeemable
|
||||
# should be wrapped in a TRANSACTION
|
||||
|
|
@ -73,20 +152,14 @@ module JamRuby
|
|||
def self.add_jam_track_to_cart(any_user, jam_track)
|
||||
cart = nil
|
||||
ShoppingCart.transaction do
|
||||
# does this user already have this JamTrack in their cart? If so, don't add it.
|
||||
|
||||
duplicate_found = false
|
||||
any_user.shopping_carts.each do |shopping_cart|
|
||||
if shopping_cart.cart_type == JamTrack::PRODUCT_TYPE && shopping_cart.cart_id == jam_track.id
|
||||
duplicate_found = true
|
||||
return
|
||||
end
|
||||
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
|
||||
end
|
||||
|
||||
unless duplicate_found
|
||||
mark_redeem = ShoppingCart.user_has_redeemable_jam_track?(any_user)
|
||||
cart = ShoppingCart.create(any_user, jam_track, 1, mark_redeem)
|
||||
end
|
||||
mark_redeem = ShoppingCart.user_has_redeemable_jam_track?(any_user)
|
||||
cart = ShoppingCart.create(any_user, jam_track, 1, mark_redeem)
|
||||
end
|
||||
cart
|
||||
end
|
||||
|
|
@ -106,7 +179,13 @@ module JamRuby
|
|||
carts[0].save
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def port(user, anonymous_user)
|
||||
|
||||
ShoppingCart.transaction do
|
||||
move_to_user(user, anonymous_user, anonymous_user.shopping_carts)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue