Merge branch 'feature/musician_profile_enhancements' into feature/musician_search2

This commit is contained in:
Jonathan Kolyer 2015-06-04 05:59:31 +00:00
commit 3eca6aeee5
187 changed files with 24565 additions and 553 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 %>

View File

@ -289,5 +289,7 @@ payment_history.sql
jam_track_right_private_key.sql
first_downloaded_jamtrack_at.sql
signing.sql
enhance_band_profile.sql
optimized_redeemption.sql
optimized_redemption_warn_mode.sql
affiliate_partners2.sql
enhance_band_profile.sql

View File

@ -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;

View File

@ -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);

View File

@ -59,8 +59,9 @@ 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"
@ -104,6 +105,9 @@ 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"
@ -203,6 +207,12 @@ 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"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -28,7 +28,7 @@ module JamRuby
end
def signup_hint
SignupHint.find_by_anonymous_user_id(@id)
SignupHint.where(anonymous_user_id: @id).where('expires_at > ?', Time.now).first
end
end
end

View File

@ -180,7 +180,7 @@ module JamRuby
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)
unless band.new_record? || band.users.exists?(user)
raise JamPermissionError, ValidationMessages::USER_NOT_BAND_MEMBER_VALIDATION_ERROR
end
@ -201,12 +201,33 @@ module JamRuby
end
band.skip_genre_validation = true unless params[:validate_genres]
puts "SKIPPING GENRE VALIDATION: #{band.skip_genre_validation}"
unless band.new_record?
OnlinePresence.delete_all(["player_id = ?", band.id])
PerformanceSample.delete_all(["player_id = ?", band.id])
end
online_presences = params[:online_presences]
Rails.logger.info("ONLINE_PRESENCE: #{online_presences.inspect} / #{params.inspect}")
if online_presences.present?
online_presences.each do |op|
new_presence = OnlinePresence.create(band, op, false)
band.online_presences << new_presence
end
end
performance_samples = params[:performance_samples]
if performance_samples.present?
performance_samples.each do |ps|
band.performance_samples << PerformanceSample.create(band, ps, false)
end
end
band
end
# helper method for creating / updating a Band
def self.save(user, params)
def self.save(user, params)
band = build_band(user, params)
if band.save

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -263,5 +263,10 @@ module JamRuby
jam_track_rights.where("user_id=?", user).first
end
def short_plan_code
prefix = 'jamtrack-'
plan_code[prefix.length..-1]
end
end
end

View File

@ -229,8 +229,8 @@ module JamRuby
return "no fingerprint specified"
end
all_fingerprint = fingerprint[:all]
running_fingerprint = fingerprint[:running]
all_fingerprint = fingerprint.delete(:all)
running_fingerprint = fingerprint.delete(:running)
if all_fingerprint.blank?
return "no all fingerprint specified"
@ -240,6 +240,9 @@ module JamRuby
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
@ -250,41 +253,80 @@ module JamRuby
return "already redeemed another"
end
# can we find a jam track that belongs to someone else with the same fingerprint
match = MachineFingerprint.find_by_fingerprint(all_fingerprint)
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 match && match.user != current_user
AdminMailer.alerts(subject: "'All' fingerprint collision by #{current_user.name}",
body: "MachineFingerprint #{match.inspect}\n\nCurrent User: #{current_user.admin_url}").deliver
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
# try to record the other fingerprint
MachineFingerprint.create(running_fingerprint, current_user, MachineFingerprint::TAKEN_ON_FRAUD_CONFLICT, MachineFingerprint::PRINT_TYPE_ACTIVE, remote_ip, self)
if APP_CONFIG.error_on_fraud
return "other user has 'all' fingerprint"
end
end
if all_fingerprint != running_fingerprint
match = MachineFingerprint.find_by_fingerprint(running_fingerprint)
if match && match.user != current_user
AdminMailer.alerts(subject: "'Running' fingerprint collision by #{current_user.name}",
body: "MachineFingerprint #{match.inspect}\n\nCurrent User: #{current_user.admin_url}").deliver
# try to record the other fingerprint
MachineFingerprint.create(all_fingerprint, current_user, MachineFingerprint::TAKEN_ON_FRAUD_CONFLICT, MachineFingerprint::PRINT_TYPE_ALL, remote_ip, self)
if APP_CONFIG.error_on_fraud
return "other user has 'running' fingerprint"
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, self)
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, self)
MachineFingerprint.create(running_fingerprint, current_user, MachineFingerprint::TAKEN_ON_SUCCESSFUL_DOWNLOAD, MachineFingerprint::PRINT_TYPE_ACTIVE, remote_ip, running_fingerprint_extra, self)
end
save!
@ -297,11 +339,12 @@ module JamRuby
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').where(is_test_purchase: false).first
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

View File

@ -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

View File

@ -5,6 +5,8 @@ module JamRuby
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'
@ -15,11 +17,11 @@ module JamRuby
validates :user, presence:true
validates :when_taken, :inclusion => {:in => [TAKEN_ON_SUCCESSFUL_DOWNLOAD, TAKEN_ON_FRAUD_CONFLICT]}
validates :fingerprint, presence: true, uniqueness:true
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, jam_track_right = nil)
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
@ -27,9 +29,21 @@ module JamRuby
mf.print_type = print_type
mf.remote_ip = remote_ip
mf.jam_track_right = jam_track_right
unless mf.save
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

View File

@ -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}'")

View File

@ -21,12 +21,13 @@ module JamRuby
OnlinePresence.where("player_id = ?", options[:id])
end
def self.create(player, options = {}, save = true)
auth_player(player, options)
# Create with target_player (target_player is either user or band):
def self.create(target_player, options = {}, save = true)
auth_player(target_player, options)
raise StateError, "Missing required information" if options[:service_type].blank? || options[:username].blank?
up = OnlinePresence.new({
:player_id => player.id,
:player_id => target_player.id,
:service_type => options[:service_type],
:username => options[:username]
})
@ -35,8 +36,8 @@ module JamRuby
up
end
def self.update(player, options = {})
auth_player(player, options)
def self.update(target_player, options = {})
auth_player(target_player, options)
raise StateError, "Missing required information" if options[:service_type].blank? || options[:username].blank? || options[:id].blank?
up = OnlinePresence.find(options[:id])
@ -45,12 +46,12 @@ module JamRuby
up.save!
end
def self.delete(player, options = {})
def self.delete(target_player, options = {})
id = options[:id]
raise StateError, "Missing required information" if id.blank?
online_presence = OnlinePresence.find(id)
if online_presence.player_id != player.id
if online_presence.player_id != target_player.id
raise JamPermissionError, PERMISSION_MSG
end
@ -60,8 +61,10 @@ module JamRuby
end
private
def self.auth_player(player, options={})
raise JamPermissionError, PERMISSION_MSG if player.nil? || options[:player_id] != player.id
def self.auth_player(target_player, options={})
if target_player.nil? || options[:player_id] != target_player.id
raise JamPermissionError, PERMISSION_MSG
end
end
end
end

View File

@ -41,12 +41,13 @@ module JamRuby
PerformanceSample.where("player_id = ?", options[:id])
end
def self.create(current_user, options = {}, save = true)
auth_user(current_user, options)
# Create with target_player (target_player is either user or band):
def self.create(target_player, options = {}, save = true)
auth_user(target_player, options)
raise StateError, "Missing required information" if options[:service_type].blank?
ps = PerformanceSample.new({
:player_id => current_user.id,
:player_id => target_player.id,
:service_type => options[:service_type],
:claimed_recording_id => options[:claimed_recording_id],
:service_id => options[:service_id],
@ -58,15 +59,15 @@ module JamRuby
ps
end
def self.delete(current_user, options = {})
raise JamPermissionError, "You do not have permission to perform this operation" if current_user.nil? || options[:player_id] != current_user.id
def self.delete(target_player, options = {})
raise JamPermissionError, "You do not have permission to perform this operation" if target_player.nil? || options[:player_id] != target_player.id
raise StateError, "The performance sample ID is missing." if options[:id].blank?
PerformanceSample.destroy(options[:id])
end
private
def self.auth_user(current_user, options={})
raise JamPermissionError, PERMISSION_MSG if current_user.nil? || options[:player_id] != current_user.id
def self.auth_user(target_player, options={})
raise JamPermissionError, PERMISSION_MSG if target_player.nil? || options[:player_id] != target_player.id
end
end
end

View File

@ -100,9 +100,14 @@ module JamRuby
if sale && sale.is_jam_track_sale?
if sale.sale_line_items.length == 1
if sale.recurly_total_in_cents == transaction.amount_in_cents
jam_track = sale.sale_line_items[0].product
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

View File

@ -8,7 +8,8 @@ module JamRuby
belongs_to :sale, class_name: 'JamRuby::Sale'
belongs_to :jam_track, class_name: 'JamRuby::JamTrack'
belongs_to :jam_track_right, class_name: 'JamRuby::JamTrackRight'
has_many :recurly_transactions, class_name: 'JamRuby::RecurlyTransactionWebHook', inverse_of: :sale_line_item, foreign_key: 'subscription_id', primary_key: 'recurly_subscription_uuid'
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}
@ -16,6 +17,7 @@ module JamRuby
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
@ -76,6 +78,16 @@ module JamRuby
sale_line_item.recurly_subscription_uuid = recurly_subscription_uuid
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

View File

@ -144,7 +144,7 @@ module JamRuby
has_many :event_sessions, :class_name => "JamRuby::EventSession"
# affiliate_partner
has_one :affiliate_partner, :class_name => "JamRuby::AffiliatePartner", :foreign_key => :partner_user_id
has_one :affiliate_partner, :class_name => "JamRuby::AffiliatePartner", :foreign_key => :partner_user_id, inverse_of: :partner_user
belongs_to :affiliate_referral, :class_name => "JamRuby::AffiliatePartner", :foreign_key => :affiliate_referral_id, :counter_cache => :referral_user_count
# diagnostics
has_many :diagnostics, :class_name => "JamRuby::Diagnostic"
@ -992,6 +992,7 @@ module JamRuby
any_user = options[:any_user]
reuse_card = options[:reuse_card]
signup_hint = options[:signup_hint]
affiliate_partner = options[:affiliate_partner]
user = User.new
@ -1126,6 +1127,14 @@ module JamRuby
if user.errors.any?
raise ActiveRecord::Rollback
else
# if the partner ID was present and the partner doesn't already have a user associated, associate this new user with the affiliate partner
if affiliate_partner && affiliate_partner.partner_user.nil?
affiliate_partner.partner_user = user
unless affiliate_partner.save
@@log.error("unable to associate #{user.to_s} with affiliate_partner #{affiliate_partner.id} / #{affiliate_partner.partner_name}")
end
end
if user.affiliate_referral = AffiliatePartner.find_by_id(affiliate_referral_id)
user.save
end if affiliate_referral_id.present?
@ -1641,6 +1650,15 @@ module JamRuby
options
end
def should_attribute_sale?(shopping_cart)
if affiliate_referral
referral_info = affiliate_referral.should_attribute_sale?(shopping_cart)
else
false
end
end
private
def create_remember_token
self.remember_token = SecureRandom.urlsafe_base64

View File

@ -0,0 +1,31 @@
require 'json'
require 'resque'
require 'resque-retry'
require 'net/http'
require 'digest/md5'
module JamRuby
# periodically scheduled to find jobs that need retrying
class TallyAffiliates
extend Resque::Plugins::JamLonelyJob
@queue = :tally_affiliates
@@log = Logging.logger[TallyAffiliates]
def self.lock_timeout
# this should be enough time to make sure the job has finished, but not so long that the system isn't recovering from a abandoned job
120
end
def self.perform
@@log.debug("waking up")
AffiliatePartner.tally_up(Date.today)
@@log.debug("done")
end
end
end

View File

@ -797,4 +797,33 @@ FactoryGirl.define do
transaction_type JamRuby::RecurlyTransactionWebHook::FAILED_PAYMENT
end
end
factory :affiliate_partner, class: 'JamRuby::AffiliatePartner' do
sequence(:partner_name) { |n| "partner-#{n}" }
entity_type 'Individual'
signed_at Time.now
association :partner_user, factory: :user
end
factory :affiliate_quarterly_payment, class: 'JamRuby::AffiliateQuarterlyPayment' do
year 2015
quarter 0
association :affiliate_partner, factory: :affiliate_partner
end
factory :affiliate_monthly_payment, class: 'JamRuby::AffiliateMonthlyPayment' do
year 2015
month 0
association :affiliate_partner, factory: :affiliate_partner
end
factory :affiliate_referral_visit, class: 'JamRuby::AffiliateReferralVisit' do
ip_address '1.1.1.1'
association :affiliate_partner, factory: :affiliate_partner
end
factory :affiliate_legalese, class: 'JamRuby::AffiliateLegalese' do
legalese Faker::Lorem.paragraphs(6).join("\n\n")
end
end

View File

@ -3,47 +3,46 @@ require 'spec_helper'
describe AffiliatePartner do
let!(:user) { FactoryGirl.create(:user) }
let!(:partner) {
AffiliatePartner.create_with_params({:partner_name => 'partner',
:partner_code => 'code',
:user_email => user.email})
}
let(:partner) { FactoryGirl.create(:affiliate_partner) }
let!(:legalese) { FactoryGirl.create(:affiliate_legalese) }
let(:jam_track) {FactoryGirl.create(:jam_track) }
# Faker::Lorem.word is tripping up the PARTNER_CODE_REGEX. We should not use it.
it 'validates required fields' do
pending
expect(partner.referral_user_count).to eq(0)
expect(partner.partner_user).to eq(user)
user.reload
expect(user.affiliate_partner).to eq(partner)
describe "unpaid" do
it "succeeds with no data" do
AffiliatePartner.unpaid.length.should eq(0)
end
oo = AffiliatePartner.create_with_params({:partner_name => Faker::Company.name,
:partner_code => 'a',
:user_email => user.email})
expect(oo.errors.messages[:partner_code][0]).to eq('is invalid')
oo = AffiliatePartner.create_with_params({:partner_name => Faker::Company.name,
:partner_code => 'foo bar',
:user_email => user.email})
expect(oo.errors.messages[:partner_code][0]).to eq('is invalid')
oo = AffiliatePartner.create_with_params({:partner_name => '',
:partner_code => Faker::Lorem.word,
:user_email => user.email})
expect(oo.errors.messages[:partner_name][0]).to eq("can't be blank")
oo = AffiliatePartner.create_with_params({:partner_name => '',
:partner_code => Faker::Lorem.word,
:user_email => Faker::Internet.email})
expect(oo.errors.messages[:partner_user][0]).to eq("can't be blank")
it "finds one unpaid partner" do
quarter = FactoryGirl.create(:affiliate_quarterly_payment, affiliate_partner: partner, closed:true, paid:false, due_amount_in_cents: AffiliatePartner::PAY_THRESHOLD)
AffiliatePartner.unpaid.should eq([partner])
end
code = Faker::Lorem.word.upcase
oo = AffiliatePartner.create_with_params({:partner_name => Faker::Company.name,
:partner_code => " #{code} ",
:user_email => user.email})
expect(oo.partner_code).to eq(code.downcase)
it "finds one unpaid partner with two quarters that exceed threshold" do
# this $5 quarter is not enough to make the threshold
quarter = FactoryGirl.create(:affiliate_quarterly_payment, affiliate_partner: partner, year:2016, closed:true, paid:false, due_amount_in_cents: AffiliatePartner::PAY_THRESHOLD / 2)
AffiliatePartner.unpaid.should eq([])
# this should get the user over the hump
quarter = FactoryGirl.create(:affiliate_quarterly_payment, affiliate_partner: partner, year:2015, closed:true, paid:false, due_amount_in_cents: AffiliatePartner::PAY_THRESHOLD / 2)
AffiliatePartner.unpaid.should eq([partner])
end
it "does not find paid or closed quarters" do
quarter = FactoryGirl.create(:affiliate_quarterly_payment, affiliate_partner: partner, year:2016, closed:true, paid:true, due_amount_in_cents: AffiliatePartner::PAY_THRESHOLD)
AffiliatePartner.unpaid.should eq([])
quarter = FactoryGirl.create(:affiliate_quarterly_payment, affiliate_partner: partner, year:2015, closed:false, paid:true, due_amount_in_cents: AffiliatePartner::PAY_THRESHOLD)
AffiliatePartner.unpaid.should eq([])
end
end
it "user-partner association" do
user_partner = FactoryGirl.create(:user, affiliate_partner: partner)
user_partner.affiliate_partner.should_not be_nil
user_partner.affiliate_partner.present?.should be_true
end
it 'has user referrals' do
pending
expect(AffiliatePartner.coded_id(partner.partner_code)).to eq(partner.id)
expect(partner.referral_user_count).to eq(0)
uu = FactoryGirl.create(:user)
uu.affiliate_referral = partner
@ -73,4 +72,766 @@ describe AffiliatePartner do
expect(by_date[keys.last]).to eq(2)
end
it 'updates address correctly' do
addy = partner.address.clone
addy[AffiliatePartner::KEY_ADDR1] = Faker::Address.street_address
addy[AffiliatePartner::KEY_ADDR2] = Faker::Address.secondary_address
addy[AffiliatePartner::KEY_CITY] = Faker::Address.city
addy[AffiliatePartner::KEY_STATE] = Faker::Address.state_abbr
addy[AffiliatePartner::KEY_COUNTRY] = Faker::Address.country
partner.update_address_value(AffiliatePartner::KEY_ADDR1, addy[AffiliatePartner::KEY_ADDR1])
partner.update_address_value(AffiliatePartner::KEY_ADDR2, addy[AffiliatePartner::KEY_ADDR2])
partner.update_address_value(AffiliatePartner::KEY_CITY, addy[AffiliatePartner::KEY_CITY])
partner.update_address_value(AffiliatePartner::KEY_STATE, addy[AffiliatePartner::KEY_STATE])
partner.update_address_value(AffiliatePartner::KEY_COUNTRY, addy[AffiliatePartner::KEY_COUNTRY])
expect(partner.address[AffiliatePartner::KEY_ADDR1]).to eq(addy[AffiliatePartner::KEY_ADDR1])
expect(partner.address[AffiliatePartner::KEY_ADDR2]).to eq(addy[AffiliatePartner::KEY_ADDR2])
expect(partner.address[AffiliatePartner::KEY_CITY]).to eq(addy[AffiliatePartner::KEY_CITY])
expect(partner.address[AffiliatePartner::KEY_STATE]).to eq(addy[AffiliatePartner::KEY_STATE])
expect(partner.address[AffiliatePartner::KEY_COUNTRY]).to eq(addy[AffiliatePartner::KEY_COUNTRY])
end
it 'associates legalese' do
end
describe "should_attribute_sale?" do
it "user with no affiliate relationship" do
shopping_cart = ShoppingCart.create user, jam_track, 1
user.should_attribute_sale?(shopping_cart).should be_false
end
it "user with an affiliate relationship buying a jamtrack" do
user.affiliate_referral = partner
user.save!
shopping_cart = ShoppingCart.create user, jam_track, 1, false
user.should_attribute_sale?(shopping_cart).should eq({fee_in_cents:20})
end
it "user with an affiliate relationship redeeming a jamtrack" do
user.affiliate_referral = partner
user.save!
shopping_cart = ShoppingCart.create user, jam_track, 1, true
user.should_attribute_sale?(shopping_cart).should eq({fee_in_cents:0})
end
it "user with an expired affiliate relationship redeeming a jamtrack" do
user.affiliate_referral = partner
user.created_at = (365 * 2 + 1).days.ago
user.save!
shopping_cart = ShoppingCart.create user, jam_track, 1, false
user.should_attribute_sale?(shopping_cart).should be_false
end
end
describe "created_within_affiliate_window" do
it "user created very recently" do
partner.created_within_affiliate_window(user, Time.now).should be_true
end
it "user created 2 years, 1 day asgo" do
days_future = 365 * 2 + 1
partner.created_within_affiliate_window(user, days_future.days.from_now).should be_false
end
end
describe "tally_up" do
let(:partner1) {FactoryGirl.create(:affiliate_partner)}
let(:partner2) {FactoryGirl.create(:affiliate_partner)}
let(:payment1) {FactoryGirl.create(:affiliate_quarterly_payment, affiliate_partner: partner1)}
let(:user_partner1) { FactoryGirl.create(:user, affiliate_referral: partner1)}
let(:user_partner2) { FactoryGirl.create(:user, affiliate_referral: partner2)}
let(:sale) {Sale.create_jam_track_sale(user_partner1)}
describe "ensure_quarters_exist" do
it "runs OK with no data" do
AffiliatePartner.ensure_quarters_exist(2015, 0)
AffiliateQuarterlyPayment.count.should eq(0)
end
it "creates new slots" do
partner1.touch
partner2.touch
AffiliatePartner.ensure_quarters_exist(2015, 0)
AffiliateQuarterlyPayment.count.should eq(2)
quarter = partner1.quarters.first
quarter.year.should eq(2015)
quarter.quarter.should eq(0)
quarter = partner2.quarters.first
quarter.year.should eq(2015)
quarter.quarter.should eq(0)
end
it "creates one slot, ignoring other" do
partner1.touch
partner2.touch
payment1.touch
AffiliateQuarterlyPayment.count.should eq(1)
AffiliatePartner.ensure_quarters_exist(2015, 0)
AffiliateQuarterlyPayment.count.should eq(2)
quarter = partner1.quarters.first
quarter.year.should eq(2015)
quarter.quarter.should eq(0)
quarter = partner2.quarters.first
quarter.year.should eq(2015)
quarter.quarter.should eq(0)
end
end
describe "close_quarters" do
it "runs OK with no data" do
AffiliateQuarterlyPayment.count.should eq(0)
AffiliatePartner.close_quarters(2015, 1)
end
it "ignores current quarter" do
partner1.touch
partner2.touch
AffiliatePartner.ensure_quarters_exist(2015, 0)
AffiliateQuarterlyPayment.count.should eq(2)
AffiliatePartner.close_quarters(2015, 0)
AffiliateQuarterlyPayment.where(closed: true).count.should eq(0)
end
it "closes previous quarter" do
partner1.touch
partner2.touch
AffiliatePartner.ensure_quarters_exist(2015, 0)
AffiliateQuarterlyPayment.count.should eq(2)
AffiliatePartner.close_quarters(2015, 1)
AffiliateQuarterlyPayment.where(closed: true).count.should eq(2)
end
it "closes previous quarter (edge case)" do
partner1.touch
partner2.touch
AffiliatePartner.ensure_quarters_exist(2014, 3)
AffiliateQuarterlyPayment.count.should eq(2)
AffiliatePartner.close_quarters(2015, 0)
AffiliateQuarterlyPayment.where(closed: true).count.should eq(2)
end
end
describe "tally_partner_totals" do
it "runs OK with no data" do
AffiliatePartner.tally_partner_totals
AffiliatePartner.count.should eq(0)
end
it "updates partner when there is no data to tally" do
partner1.touch
partner1.cumulative_earnings_in_cents.should eq(0)
partner1.referral_user_count.should eq(0)
AffiliatePartner.tally_partner_totals
partner1.reload
partner1.cumulative_earnings_in_cents.should eq(0)
partner1.referral_user_count.should eq(0)
end
it "updates referral_user_count" do
FactoryGirl.create(:user, affiliate_referral: partner1)
AffiliatePartner.tally_partner_totals
partner1.reload
partner1.referral_user_count.should eq(1)
partner1.cumulative_earnings_in_cents.should eq(0)
FactoryGirl.create(:user, affiliate_referral: partner2)
AffiliatePartner.tally_partner_totals
partner1.reload
partner1.referral_user_count.should eq(1)
partner1.cumulative_earnings_in_cents.should eq(0)
partner2.reload
partner2.referral_user_count.should eq(1)
partner2.cumulative_earnings_in_cents.should eq(0)
FactoryGirl.create(:user, affiliate_referral: partner2)
AffiliatePartner.tally_partner_totals
partner1.reload
partner1.referral_user_count.should eq(1)
partner1.cumulative_earnings_in_cents.should eq(0)
partner2.reload
partner2.referral_user_count.should eq(2)
partner2.cumulative_earnings_in_cents.should eq(0)
end
it "updates cumulative_earnings_in_cents" do
FactoryGirl.create(:affiliate_quarterly_payment, affiliate_partner:partner1, year:2015, quarter:0, due_amount_in_cents: 0, closed:true, paid:true)
AffiliatePartner.tally_partner_totals
partner1.reload
partner1.referral_user_count.should eq(0)
partner1.cumulative_earnings_in_cents.should eq(0)
FactoryGirl.create(:affiliate_quarterly_payment, affiliate_partner:partner2, year:2015, quarter:0, due_amount_in_cents: 10, closed:true, paid:true)
AffiliatePartner.tally_partner_totals
partner1.reload
partner1.referral_user_count.should eq(0)
partner1.cumulative_earnings_in_cents.should eq(0)
partner2.reload
partner2.referral_user_count.should eq(0)
partner2.cumulative_earnings_in_cents.should eq(10)
FactoryGirl.create(:affiliate_quarterly_payment, affiliate_partner:partner2, year:2015, quarter:1, due_amount_in_cents: 100, closed:true, paid:true)
AffiliatePartner.tally_partner_totals
partner1.reload
partner1.referral_user_count.should eq(0)
partner1.cumulative_earnings_in_cents.should eq(0)
partner2.reload
partner2.referral_user_count.should eq(0)
partner2.cumulative_earnings_in_cents.should eq(110)
FactoryGirl.create(:affiliate_quarterly_payment, affiliate_partner:partner1, year:2015, quarter:1, due_amount_in_cents: 100, closed:true, paid:true)
AffiliatePartner.tally_partner_totals
partner1.reload
partner1.referral_user_count.should eq(0)
partner1.cumulative_earnings_in_cents.should eq(100)
partner2.reload
partner2.referral_user_count.should eq(0)
partner2.cumulative_earnings_in_cents.should eq(110)
# a paid=false quarterly payment does not yet reflect in cumulative earnings
FactoryGirl.create(:affiliate_quarterly_payment, affiliate_partner:partner1, year:2015, quarter:2, due_amount_in_cents: 1000, closed:false, paid:false)
AffiliatePartner.tally_partner_totals
partner1.reload
partner1.referral_user_count.should eq(0)
partner1.cumulative_earnings_in_cents.should eq(100)
partner2.reload
partner2.referral_user_count.should eq(0)
partner2.cumulative_earnings_in_cents.should eq(110)
end
end
describe "total_quarters" do
it "runs OK with no data" do
AffiliateQuarterlyPayment.count.should eq(0)
AffiliatePartner.total_quarters(2015, 0)
end
it "totals 0 with no sales data" do
partner1.touch
partner2.touch
AffiliatePartner.ensure_quarters_exist(2015, 0)
AffiliateQuarterlyPayment.count.should eq(2)
AffiliatePartner.total_quarters(2015, 0)
quarter = partner1.quarters.first
quarter.due_amount_in_cents.should eq(0)
quarter.last_updated.should_not be_nil
quarter = partner2.quarters.first
quarter.due_amount_in_cents.should eq(0)
quarter.last_updated.should_not be_nil
end
it "totals with sales data" do
partner1.touch
partner2.touch
# create a freebie for partner1
shopping_cart = ShoppingCart.create user_partner1, jam_track, 1, true
freebie_sale = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, nil, nil)
freebie_sale.affiliate_referral_fee_in_cents.should eq(0)
freebie_sale.created_at = Date.new(2015, 1, 1)
freebie_sale.save!
AffiliatePartner.ensure_quarters_exist(2015, 0)
AffiliateQuarterlyPayment.count.should eq(2)
AffiliatePartner.total_quarters(2015, 0)
quarter = partner1.quarters.first
quarter.due_amount_in_cents.should eq(0)
quarter.jamtracks_sold.should eq(0)
quarter = partner2.quarters.first
quarter.due_amount_in_cents.should eq(0)
quarter.jamtracks_sold.should eq(0)
# create a real sale for partner1
shopping_cart = ShoppingCart.create user_partner1, jam_track, 1, false
real_sale = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, nil, nil)
real_sale.affiliate_referral_fee_in_cents.should eq(20)
real_sale.created_at = Date.new(2015, 1, 1)
real_sale.save!
AffiliatePartner.ensure_quarters_exist(2015, 0)
AffiliatePartner.total_quarters(2015, 0)
quarter = partner1.quarters.first
quarter.due_amount_in_cents.should eq(20)
quarter.jamtracks_sold.should eq(1)
quarter = partner2.quarters.first
quarter.due_amount_in_cents.should eq(0)
quarter.jamtracks_sold.should eq(0)
# create a real sale for partner2
shopping_cart = ShoppingCart.create user_partner2, jam_track, 1, false
real_sale = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, nil, nil)
real_sale.affiliate_referral_fee_in_cents.should eq(20)
real_sale.created_at = Date.new(2015, 1, 1)
real_sale.save!
AffiliatePartner.ensure_quarters_exist(2015, 0)
AffiliatePartner.total_quarters(2015, 0)
quarter = partner1.quarters.first
quarter.due_amount_in_cents.should eq(20)
quarter.jamtracks_sold.should eq(1)
quarter = partner2.quarters.first
quarter.due_amount_in_cents.should eq(20)
quarter.jamtracks_sold.should eq(1)
# create a real sale for partner1
shopping_cart = ShoppingCart.create user_partner1, jam_track, 1, false
real_sale = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, nil, nil)
real_sale.affiliate_referral_fee_in_cents.should eq(20)
real_sale.created_at = Date.new(2015, 1, 1)
real_sale.save!
AffiliatePartner.ensure_quarters_exist(2015, 0)
AffiliatePartner.total_quarters(2015, 0)
quarter = partner1.quarters.first
quarter.due_amount_in_cents.should eq(40)
quarter.jamtracks_sold.should eq(2)
quarter = partner2.quarters.first
quarter.due_amount_in_cents.should eq(20)
quarter.jamtracks_sold.should eq(1)
# create a real sale for a non-affiliated user
shopping_cart = ShoppingCart.create user, jam_track, 1, false
real_sale = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, nil, nil)
real_sale.affiliate_referral_fee_in_cents.should be_nil
real_sale.created_at = Date.new(2015, 1, 1)
real_sale.save!
AffiliatePartner.ensure_quarters_exist(2015, 0)
AffiliatePartner.total_quarters(2015, 0)
quarter = partner1.quarters.first
quarter.due_amount_in_cents.should eq(40)
quarter.jamtracks_sold.should eq(2)
quarter = partner2.quarters.first
quarter.due_amount_in_cents.should eq(20)
quarter.jamtracks_sold.should eq(1)
# create a real sale but in previous quarter (should no have effect on the quarter being computed)
shopping_cart = ShoppingCart.create user_partner1, jam_track, 1, false
real_sale = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, nil, nil)
real_sale.affiliate_referral_fee_in_cents.should eq(20)
real_sale.created_at = Date.new(2014, 12, 31)
real_sale.save!
AffiliatePartner.ensure_quarters_exist(2015, 0)
AffiliatePartner.total_quarters(2015, 0)
quarter = partner1.quarters.first
quarter.due_amount_in_cents.should eq(40)
quarter.jamtracks_sold.should eq(2)
quarter = partner2.quarters.first
quarter.due_amount_in_cents.should eq(20)
quarter.jamtracks_sold.should eq(1)
# create a real sale but in later quarter (should no have effect on the quarter being computed)
shopping_cart = ShoppingCart.create user_partner1, jam_track, 1, false
real_sale = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, nil, nil)
real_sale.affiliate_referral_fee_in_cents.should eq(20)
real_sale.created_at = Date.new(2015, 4, 1)
real_sale.save!
real_sale_later = real_sale
AffiliatePartner.ensure_quarters_exist(2015, 0)
AffiliatePartner.total_quarters(2015, 0)
quarter = partner1.quarters.first
quarter.due_amount_in_cents.should eq(40)
quarter.jamtracks_sold.should eq(2)
quarter = partner2.quarters.first
quarter.due_amount_in_cents.should eq(20)
quarter.jamtracks_sold.should eq(1)
# create a real sale but then refund it
shopping_cart = ShoppingCart.create user_partner1, jam_track, 1, false
real_sale = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, nil, nil)
real_sale.affiliate_referral_fee_in_cents.should eq(20)
real_sale.created_at = Date.new(2015, 3, 31)
real_sale.save!
AffiliatePartner.ensure_quarters_exist(2015, 0)
AffiliatePartner.total_quarters(2015, 0)
quarter = partner1.quarters.first
quarter.due_amount_in_cents.should eq(60)
quarter.jamtracks_sold.should eq(3)
quarter = partner2.quarters.first
quarter.due_amount_in_cents.should eq(20)
# now refund it
real_sale.affiliate_refunded_at = Date.new(2015, 3, 1)
real_sale.affiliate_refunded = true
real_sale.save!
AffiliatePartner.ensure_quarters_exist(2015, 0)
AffiliatePartner.total_quarters(2015, 0)
quarter = partner1.quarters.first
quarter.due_amount_in_cents.should eq(40)
quarter = partner2.quarters.first
quarter.due_amount_in_cents.should eq(20)
quarter.jamtracks_sold.should eq(1)
# create the 2nd quarter, which should add up the sale created a few bits up
AffiliatePartner.ensure_quarters_exist(2015, 1)
AffiliatePartner.total_quarters(2015, 1)
payment = AffiliateQuarterlyPayment.find_by_quarter_and_year_and_affiliate_partner_id!(1, 2015, partner1.id)
payment.due_amount_in_cents.should eq(20)
# and now refund it in the 3rd quarter
real_sale_later.affiliate_refunded_at = Date.new(2015, 7, 1)
real_sale_later.affiliate_refunded = true
real_sale_later.save!
AffiliatePartner.total_quarters(2015, 1)
payment = AffiliateQuarterlyPayment.find_by_quarter_and_year_and_affiliate_partner_id!(1, 2015, partner1.id)
payment.due_amount_in_cents.should eq(20)
payment.jamtracks_sold.should eq(1)
# now catch the one refund in the 3rd quarter
AffiliatePartner.ensure_quarters_exist(2015, 2)
AffiliatePartner.total_quarters(2015, 2)
payment = AffiliateQuarterlyPayment.find_by_quarter_and_year_and_affiliate_partner_id!(2, 2015, partner1.id)
payment.due_amount_in_cents.should eq(-20)
payment.jamtracks_sold.should eq(-1)
end
end
describe "tally_up complete" do
it "runs OK with no data" do
AffiliatePartner.tally_up(Date.new(2015, 1, 1))
end
it "successive runs" do
GenericState.singleton.affiliate_tallied_at.should be_nil
AffiliatePartner.tally_up(Date.new(2015, 1, 1))
GenericState.singleton.affiliate_tallied_at.should_not be_nil
AffiliateQuarterlyPayment.count.should eq(0)
# partner is created
partner1.touch
AffiliatePartner.tally_up(Date.new(2015, 1, 1))
AffiliateQuarterlyPayment.count.should eq(2)
AffiliateMonthlyPayment.count.should eq(6)
quarter_previous = AffiliateQuarterlyPayment.find_by_quarter_and_year_and_affiliate_partner_id!(3, 2014, partner1.id)
quarter_previous.due_amount_in_cents.should eq(0)
quarter_previous.closed.should be_true
quarter = AffiliateQuarterlyPayment.find_by_quarter_and_year_and_affiliate_partner_id!(0, 2015, partner1.id)
quarter.due_amount_in_cents.should eq(0)
quarter.closed.should be_false
month_previous= AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(10, 2014, partner1.id)
month_previous.due_amount_in_cents.should eq(0)
month_previous.closed.should be_true
month_previous.jamtracks_sold.should eq(0)
month_previous= AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(11, 2014, partner1.id)
month_previous.due_amount_in_cents.should eq(0)
month_previous.closed.should be_true
month_previous.jamtracks_sold.should eq(0)
month_previous= AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(12, 2014, partner1.id)
month_previous.due_amount_in_cents.should eq(0)
month_previous.closed.should be_true
month_previous.jamtracks_sold.should eq(0)
month = AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(1, 2015, partner1.id)
month_previous.due_amount_in_cents.should eq(0)
month_previous.closed.should be_true
month_previous.jamtracks_sold.should eq(0)
month = AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(2, 2015, partner1.id)
month_previous.due_amount_in_cents.should eq(0)
month_previous.closed.should be_true
month.jamtracks_sold.should eq(0)
month = AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(3, 2015, partner1.id)
month_previous.due_amount_in_cents.should eq(0)
month_previous.closed.should be_true
month_previous.jamtracks_sold.should eq(0)
shopping_cart = ShoppingCart.create user_partner1, jam_track, 1, false
real_sale = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, nil, nil)
real_sale.affiliate_referral_fee_in_cents.should eq(20)
real_sale.created_at = Date.new(2015, 4, 1)
real_sale.save!
AffiliatePartner.tally_up(Date.new(2015, 4, 1))
AffiliateQuarterlyPayment.count.should eq(3)
quarter = AffiliateQuarterlyPayment.find_by_quarter_and_year_and_affiliate_partner_id!(0, 2015, partner1.id)
quarter.due_amount_in_cents.should eq(0)
quarter.jamtracks_sold.should eq(0)
quarter.closed.should be_true
quarter2 = AffiliateQuarterlyPayment.find_by_quarter_and_year_and_affiliate_partner_id!(1, 2015, partner1.id)
quarter2.due_amount_in_cents.should eq(20)
quarter2.jamtracks_sold.should eq(1)
quarter2.closed.should be_false
month = AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(1, 2015, partner1.id)
month.due_amount_in_cents.should eq(0)
month.jamtracks_sold.should eq(0)
month.closed.should be_true
month = AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(2, 2015, partner1.id)
month.due_amount_in_cents.should eq(0)
month.jamtracks_sold.should eq(0)
month.closed.should be_true
month = AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(3, 2015, partner1.id)
month.due_amount_in_cents.should eq(0)
month.jamtracks_sold.should eq(0)
month.closed.should be_true
month = AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(4, 2015, partner1.id)
month.due_amount_in_cents.should eq(20)
month.jamtracks_sold.should eq(1)
month.closed.should be_false
month = AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(5, 2015, partner1.id)
month.due_amount_in_cents.should eq(0)
month.jamtracks_sold.should eq(0)
month.closed.should be_false
month = AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(6, 2015, partner1.id)
month.due_amount_in_cents.should eq(0)
month.jamtracks_sold.should eq(0)
month.closed.should be_false
# now sneak in a purchase in the 1st quarter, which makes no sense, but proves that closed quarters are not touched
shopping_cart = ShoppingCart.create user_partner1, jam_track, 1, false
real_sale = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, nil, nil)
real_sale.affiliate_referral_fee_in_cents.should eq(20)
real_sale.created_at = Date.new(2015, 1, 1)
real_sale.save!
AffiliatePartner.tally_up(Date.new(2015, 4, 2))
quarter = AffiliateQuarterlyPayment.find_by_quarter_and_year_and_affiliate_partner_id!(0, 2015, partner1.id)
quarter.due_amount_in_cents.should eq(0)
quarter.jamtracks_sold.should eq(0)
quarter.closed.should be_true
month = AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(1, 2015, partner1.id)
month.due_amount_in_cents.should eq(0)
month.closed.should be_true
month = AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(2, 2015, partner1.id)
month.due_amount_in_cents.should eq(0)
month.jamtracks_sold.should eq(0)
month.closed.should be_true
month = AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(3, 2015, partner1.id)
month.due_amount_in_cents.should eq(0)
month.jamtracks_sold.should eq(0)
month.closed.should be_true
end
end
describe "tally_traffic_totals" do
it "runs OK with no data" do
AffiliatePartner.tally_traffic_totals(Date.yesterday, Date.today)
end
it "can deal with simple signup case" do
user_partner1.touch
day0 = user_partner1.created_at.to_date
# simulate what happens when this scheduled job first ever runs
AffiliatePartner.tally_traffic_totals(nil, day0)
AffiliateTrafficTotal.count.should eq(1)
traffic_total_day_before = AffiliateTrafficTotal.find_by_day_and_affiliate_partner_id(day0 - 1, user_partner1.affiliate_referral_id)
traffic_total_day_before.visits.should eq(0)
traffic_total_day_before.signups.should eq(0)
# then simulate when it runs on the same day as it ran on the day before
AffiliatePartner.tally_traffic_totals(day0, day0)
AffiliateTrafficTotal.count.should eq(1)
traffic_total_day_before = AffiliateTrafficTotal.find_by_day_and_affiliate_partner_id(day0 - 1, user_partner1.affiliate_referral_id)
traffic_total_day_before.visits.should eq(0)
traffic_total_day_before.signups.should eq(0)
# now run it on the next day, which should catch the signup event
day1 = day0 + 1
AffiliatePartner.tally_traffic_totals(day0, day1)
AffiliateTrafficTotal.count.should eq(2)
traffic_total_day_before = AffiliateTrafficTotal.find_by_day_and_affiliate_partner_id(day0 - 1, user_partner1.affiliate_referral_id)
traffic_total_day_before.visits.should eq(0)
traffic_total_day_before.signups.should eq(0)
traffic_total_day0 = AffiliateTrafficTotal.find_by_day_and_affiliate_partner_id(day0, user_partner1.affiliate_referral_id)
traffic_total_day0.visits.should eq(0)
traffic_total_day0.signups.should eq(1)
# add in a visit
visit = FactoryGirl.create(:affiliate_referral_visit, affiliate_partner: user_partner1.affiliate_referral)
# it won't get seen though because we've moved on
AffiliatePartner.tally_traffic_totals(day1, day1)
AffiliateTrafficTotal.count.should eq(2)
traffic_total_day_before = AffiliateTrafficTotal.find_by_day_and_affiliate_partner_id(day0 - 1, user_partner1.affiliate_referral_id)
traffic_total_day_before.visits.should eq(0)
traffic_total_day_before.signups.should eq(0)
traffic_total_day0 = AffiliateTrafficTotal.find_by_day_and_affiliate_partner_id(day0, user_partner1.affiliate_referral_id)
traffic_total_day0.visits.should eq(0)
traffic_total_day0.signups.should eq(1)
# manipulate the visit created_at so we can record it
visit.created_at = day1
visit.save!
day2 = day1 + 1
AffiliatePartner.tally_traffic_totals(day1, day2)
AffiliateTrafficTotal.count.should eq(3)
traffic_total_day_before = AffiliateTrafficTotal.find_by_day_and_affiliate_partner_id(day0 - 1, user_partner1.affiliate_referral_id)
traffic_total_day_before.visits.should eq(0)
traffic_total_day_before.signups.should eq(0)
traffic_total_day0 = AffiliateTrafficTotal.find_by_day_and_affiliate_partner_id(day0, user_partner1.affiliate_referral_id)
traffic_total_day0.visits.should eq(0)
traffic_total_day0.signups.should eq(1)
traffic_total_day1 = AffiliateTrafficTotal.find_by_day_and_affiliate_partner_id(day1, user_partner1.affiliate_referral_id)
traffic_total_day1.visits.should eq(1)
traffic_total_day1.signups.should eq(0)
# now create 2 records on day 2 for visits and signups both, and a partner with their own visit and signup, and do a final check
user_partner2.touch
visit2 = FactoryGirl.create(:affiliate_referral_visit, affiliate_partner: user_partner1.affiliate_referral)
visit3 = FactoryGirl.create(:affiliate_referral_visit, affiliate_partner: user_partner1.affiliate_referral)
visit_partner2 = FactoryGirl.create(:affiliate_referral_visit, affiliate_partner: user_partner2.affiliate_referral)
visit2.created_at = day2
visit3.created_at = day2
visit_partner2.created_at = day2
visit2.save!
visit3.save!
visit_partner2.save!
user2 = FactoryGirl.create(:user, affiliate_referral:user_partner1.affiliate_referral)
user3 = FactoryGirl.create(:user, affiliate_referral:user_partner1.affiliate_referral)
user2.created_at = day2
user3.created_at = day2
user_partner2.created_at = day2
user2.save!
user3.save!
user_partner2.save!
day3 = day2 + 1
AffiliatePartner.tally_traffic_totals(day2, day3)
AffiliateTrafficTotal.count.should eq(5)
traffic_total_day_before = AffiliateTrafficTotal.find_by_day_and_affiliate_partner_id(day0 - 1, user_partner1.affiliate_referral_id)
traffic_total_day_before.visits.should eq(0)
traffic_total_day_before.signups.should eq(0)
traffic_total_day0 = AffiliateTrafficTotal.find_by_day_and_affiliate_partner_id(day0, user_partner1.affiliate_referral_id)
traffic_total_day0.visits.should eq(0)
traffic_total_day0.signups.should eq(1)
traffic_total_day1 = AffiliateTrafficTotal.find_by_day_and_affiliate_partner_id(day1, user_partner1.affiliate_referral_id)
traffic_total_day1.visits.should eq(1)
traffic_total_day1.signups.should eq(0)
traffic_total_day2 = AffiliateTrafficTotal.find_by_day_and_affiliate_partner_id(day2, user_partner1.affiliate_referral_id)
traffic_total_day2.visits.should eq(2)
traffic_total_day2.signups.should eq(2)
traffic_total_day2 = AffiliateTrafficTotal.find_by_day_and_affiliate_partner_id(day2, user_partner2.affiliate_referral_id)
traffic_total_day2.visits.should eq(1)
traffic_total_day2.signups.should eq(1)
end
end
describe "boundary_dates" do
it "1st quarter" do
start_date, end_date = AffiliatePartner.boundary_dates(2015, 0)
start_date.should eq(Date.new(2015, 1, 1))
end_date.should eq(Date.new(2015, 3, 31))
end
it "2nd quarter" do
start_date, end_date = AffiliatePartner.boundary_dates(2015, 1)
start_date.should eq(Date.new(2015, 4, 1))
end_date.should eq(Date.new(2015, 6, 30))
end
it "3rd quarter" do
start_date, end_date = AffiliatePartner.boundary_dates(2015, 2)
start_date.should eq(Date.new(2015, 7, 1))
end_date.should eq(Date.new(2015, 9, 30))
end
it "4th quarter" do
start_date, end_date = AffiliatePartner.boundary_dates(2015, 3)
start_date.should eq(Date.new(2015, 10, 1))
end_date.should eq(Date.new(2015, 12, 31))
end
end
describe "boundary_dates_for_month" do
it "invalid month" do
expect{AffiliatePartner.boundary_dates_for_month(2015, 0)}.to raise_error
end
it "January" do
start_date, end_date = AffiliatePartner.boundary_dates_for_month(2015, 1)
start_date.should eq(Date.new(2015, 1, 1))
end_date.should eq(Date.new(2015, 1, 31))
end
it "February" do
start_date, end_date = AffiliatePartner.boundary_dates_for_month(2015, 2)
start_date.should eq(Date.new(2015, 2, 1))
end_date.should eq(Date.new(2015, 2, 28))
end
it "March" do
start_date, end_date = AffiliatePartner.boundary_dates_for_month(2015, 3)
start_date.should eq(Date.new(2015, 3, 1))
end_date.should eq(Date.new(2015, 3, 31))
end
it "April" do
start_date, end_date = AffiliatePartner.boundary_dates_for_month(2015, 4)
start_date.should eq(Date.new(2015, 4, 1))
end_date.should eq(Date.new(2015, 4, 30))
end
it "May" do
start_date, end_date = AffiliatePartner.boundary_dates_for_month(2015, 5)
start_date.should eq(Date.new(2015, 5, 1))
end_date.should eq(Date.new(2015, 5, 31))
end
it "June" do
start_date, end_date = AffiliatePartner.boundary_dates_for_month(2015, 6)
start_date.should eq(Date.new(2015, 6, 1))
end_date.should eq(Date.new(2015, 6, 30))
end
it "July" do
start_date, end_date = AffiliatePartner.boundary_dates_for_month(2015, 7)
start_date.should eq(Date.new(2015, 7, 1))
end_date.should eq(Date.new(2015, 7, 31))
end
it "August" do
start_date, end_date = AffiliatePartner.boundary_dates_for_month(2015, 8)
start_date.should eq(Date.new(2015, 8, 1))
end_date.should eq(Date.new(2015, 8, 31))
end
it "September" do
start_date, end_date = AffiliatePartner.boundary_dates_for_month(2015, 9)
start_date.should eq(Date.new(2015, 9, 1))
end_date.should eq(Date.new(2015, 9, 30))
end
it "October" do
start_date, end_date = AffiliatePartner.boundary_dates_for_month(2015, 10)
start_date.should eq(Date.new(2015, 10, 1))
end_date.should eq(Date.new(2015, 10, 31))
end
it "November" do
start_date, end_date = AffiliatePartner.boundary_dates_for_month(2015, 11)
start_date.should eq(Date.new(2015, 11, 1))
end_date.should eq(Date.new(2015, 11, 30))
end
it "December" do
start_date, end_date = AffiliatePartner.boundary_dates_for_month(2015, 12)
start_date.should eq(Date.new(2015, 12, 1))
end_date.should eq(Date.new(2015, 12, 31))
end
it "February in a leap year" do
start_date, end_date = AffiliatePartner.boundary_dates_for_month(2016, 2)
start_date.should eq(Date.new(2016, 2, 1))
end_date.should eq(Date.new(2016, 2, 29))
end
end
end
end

View File

@ -0,0 +1,39 @@
require 'spec_helper'
describe AffiliatePayment do
let(:partner) { FactoryGirl.create(:affiliate_partner) }
let(:user_partner) { FactoryGirl.create(:user, affiliate_partner: partner) }
it "succeeds with no data" do
results, nex = AffiliatePayment.index(user_partner, {})
results.length.should eq(0)
end
it "sorts month and quarters correctly" do
monthly1 = FactoryGirl.create(:affiliate_monthly_payment, affiliate_partner: partner, closed: true, due_amount_in_cents: 10, month: 1, year: 2015)
monthly2 = FactoryGirl.create(:affiliate_monthly_payment, affiliate_partner: partner, closed: true, due_amount_in_cents: 20, month: 2, year: 2015)
monthly3 = FactoryGirl.create(:affiliate_monthly_payment, affiliate_partner: partner, closed: true, due_amount_in_cents: 30, month: 3, year: 2015)
monthly4 = FactoryGirl.create(:affiliate_monthly_payment, affiliate_partner: partner, closed: true, due_amount_in_cents: 40, month: 4, year: 2015)
quarterly = FactoryGirl.create(:affiliate_quarterly_payment, affiliate_partner: partner, closed: true, paid:true, due_amount_in_cents: 50, quarter: 0, year: 2015)
results, nex = AffiliatePayment.index(user_partner, {})
results.length.should eq(5)
result1 = results[0]
result2 = results[1]
result3 = results[2]
result4 = results[3]
result5 = results[4]
result1.payment_type.should eq('monthly')
result1.due_amount_in_cents.should eq(10)
result2.payment_type.should eq('monthly')
result2.due_amount_in_cents.should eq(20)
result3.payment_type.should eq('monthly')
result3.due_amount_in_cents.should eq(30)
result4.payment_type.should eq('quarterly')
result4.due_amount_in_cents.should eq(50)
result5.payment_type.should eq('monthly')
result5.due_amount_in_cents.should eq(40)
end
end

View File

@ -0,0 +1,40 @@
require 'spec_helper'
describe AffiliateReferralVisit do
let!(:user) { FactoryGirl.create(:user) }
let(:partner) { FactoryGirl.create(:affiliate_partner) }
let(:valid_track_options) {
{
affiliate_id: partner.id,
visited: false,
remote_ip: '1.2.2.1',
visited_url: '/',
referral_url: 'http://www.youtube.com',
current_user: nil
}
}
describe "track" do
it "succeeds" do
visit = AffiliateReferralVisit.track( valid_track_options )
visit.valid?.should be_true
end
it "never fails with error" do
visit = AffiliateReferralVisit.track( {})
visit.valid?.should be_false
options = valid_track_options
options[:affiliate_id] = 111
visit = AffiliateReferralVisit.track( options)
visit.valid?.should be_true
options = valid_track_options
options[:current_user] = user
visit = AffiliateReferralVisit.track( options)
visit.valid?.should be_true
end
end
end

View File

@ -168,7 +168,8 @@ describe JamTrackRight do
JamTrackRight.stats.should eq('count' => 1,
'signing_count' => 0,
'redeemed_count' => 0,
'purchased_count' => 1)
'purchased_count' => 1,
'redeemed_and_dl_count' => 0)
end
it "two" do
@ -178,7 +179,8 @@ describe JamTrackRight do
JamTrackRight.stats.should eq('count' => 2,
'signing_count' => 0,
'redeemed_count' => 0,
'purchased_count' => 2)
'purchased_count' => 2,
'redeemed_and_dl_count' => 0)
right1.signing_44 = true
right1.save!
@ -188,7 +190,8 @@ describe JamTrackRight do
JamTrackRight.stats.should eq('count' => 2,
'signing_count' => 2,
'redeemed_count' => 0,
'purchased_count' => 2)
'purchased_count' => 2,
'redeemed_and_dl_count' => 0)
right1.redeemed = true
right1.save!
@ -196,7 +199,8 @@ describe JamTrackRight do
JamTrackRight.stats.should eq('count' => 2,
'signing_count' => 2,
'redeemed_count' => 1,
'purchased_count' => 1)
'purchased_count' => 1,
'redeemed_and_dl_count' => 0)
right2.is_test_purchase = true
right2.save!
@ -204,7 +208,8 @@ describe JamTrackRight do
JamTrackRight.stats.should eq('count' => 1,
'signing_count' => 1,
'redeemed_count' => 1,
'purchased_count' => 0)
'purchased_count' => 0,
'redeemed_and_dl_count' => 0)
end
end
@ -213,7 +218,9 @@ describe JamTrackRight do
let(:other) {FactoryGirl.create(:user)}
let(:first_fingerprint) { {all: 'all', running: 'running' } }
let(:new_fingerprint) { {all: 'all_2', running: 'running' } }
let(:full_fingerprint) { {all: :all_3, running: :running_3, all_3: { mac: "72:00:02:4C:1E:61", name: "en2", upstate: true }, running_3: { mac: "72:00:02:4C:1E:62", name: "en3", upstate: false } } }
let(:remote_ip) {'1.1.1.1'}
let(:remote_ip2) {'2.2.2.2'}
let(:jam_track_right) { FactoryGirl.create(:jam_track_right, user: user, redeemed: true, redeemed_and_fingerprinted: false) }
let(:jam_track_right_purchased) { FactoryGirl.create(:jam_track_right, user: user, redeemed: false, redeemed_and_fingerprinted: false) }
let(:jam_track_right_other) { FactoryGirl.create(:jam_track_right, user: other, redeemed: true, redeemed_and_fingerprinted: false) }
@ -233,16 +240,16 @@ describe JamTrackRight do
jam_track_right.redeemed_and_fingerprinted.should be_true
mf = MachineFingerprint.find_by_fingerprint(first_fingerprint[:all])
mf = MachineFingerprint.find_by_fingerprint('all')
mf.user.should eq(user)
mf.fingerprint.should eq(first_fingerprint[:all])
mf.fingerprint.should eq('all')
mf.when_taken.should eq(MachineFingerprint::TAKEN_ON_SUCCESSFUL_DOWNLOAD)
mf.print_type.should eq(MachineFingerprint::PRINT_TYPE_ALL)
mf.jam_track_right.should eq(jam_track_right)
mf = MachineFingerprint.find_by_fingerprint(first_fingerprint[:running])
mf = MachineFingerprint.find_by_fingerprint('running')
mf.user.should eq(user)
mf.fingerprint.should eq(first_fingerprint[:running])
mf.fingerprint.should eq('running')
mf.when_taken.should eq(MachineFingerprint::TAKEN_ON_SUCCESSFUL_DOWNLOAD)
mf.print_type.should eq(MachineFingerprint::PRINT_TYPE_ACTIVE)
mf.jam_track_right.should eq(jam_track_right)
@ -274,40 +281,80 @@ describe JamTrackRight do
end
it "protects against re-using fingerprint across users (conflicts on all fp)" do
first_fingerprint2 = first_fingerprint.clone
jam_track_right.guard_against_fraud(user, first_fingerprint, remote_ip).should be_nil
MachineFingerprint.count.should eq(2)
first_fingerprint[:running] = 'running_2'
jam_track_right_other.guard_against_fraud(other, first_fingerprint, remote_ip).should eq("other user has 'all' fingerprint")
first_fingerprint2[:running] = 'running_2'
jam_track_right_other.guard_against_fraud(other, first_fingerprint2, remote_ip).should eq("other user has 'all' fingerprint")
mf = MachineFingerprint.find_by_fingerprint(first_fingerprint[:running])
mf.user.should eq(other)
mf.fingerprint.should eq(first_fingerprint[:running])
mf.when_taken.should eq(MachineFingerprint::TAKEN_ON_FRAUD_CONFLICT)
mf = MachineFingerprint.find_by_fingerprint('running')
mf.user.should eq(user)
mf.fingerprint.should eq('running')
mf.when_taken.should eq(MachineFingerprint::TAKEN_ON_SUCCESSFUL_DOWNLOAD)
mf.print_type.should eq(MachineFingerprint::PRINT_TYPE_ACTIVE)
mf.jam_track_right.should eq(jam_track_right_other)
MachineFingerprint.count.should eq(3)
mf.jam_track_right.should eq(jam_track_right)
MachineFingerprint.count.should eq(4)
end
it "protects against re-using fingerprint across users (conflicts on running fp)" do
first_fingerprint2 = first_fingerprint.clone
jam_track_right.guard_against_fraud(user, first_fingerprint, remote_ip).should be_nil
MachineFingerprint.count.should eq(2)
first_fingerprint[:all] = 'all_2'
jam_track_right_other.guard_against_fraud(other, first_fingerprint, remote_ip).should eq("other user has 'running' fingerprint")
first_fingerprint2[:all] = 'all_2'
jam_track_right_other.guard_against_fraud(other, first_fingerprint2, remote_ip).should eq("other user has 'running' fingerprint")
mf = MachineFingerprint.find_by_fingerprint(first_fingerprint[:all])
mf.user.should eq(other)
mf.fingerprint.should eq(first_fingerprint[:all])
mf.when_taken.should eq(MachineFingerprint::TAKEN_ON_FRAUD_CONFLICT)
mf = MachineFingerprint.find_by_fingerprint('all')
mf.user.should eq(user)
mf.fingerprint.should eq('all')
mf.when_taken.should eq(MachineFingerprint::TAKEN_ON_SUCCESSFUL_DOWNLOAD)
mf.print_type.should eq(MachineFingerprint::PRINT_TYPE_ALL)
mf.jam_track_right.should eq(jam_track_right_other)
MachineFingerprint.count.should eq(3)
mf.jam_track_right.should eq(jam_track_right)
MachineFingerprint.count.should eq(4)
FraudAlert.count.should eq(1)
fraud = FraudAlert.first
fraud.user.should eq(other)
fraud.machine_fingerprint.should eq(MachineFingerprint.where(fingerprint:'running').where(user_id:other.id).first)
end
it "ignores whitelisted fingerprint" do
whitelist = FingerprintWhitelist.new
whitelist.fingerprint = first_fingerprint[:running]
whitelist.save!
first_fingerprint2 = first_fingerprint.clone
jam_track_right.guard_against_fraud(user, first_fingerprint, remote_ip).should be_nil
MachineFingerprint.count.should eq(2)
first_fingerprint2[:all] = 'all_2'
jam_track_right_other.guard_against_fraud(other, first_fingerprint2, remote_ip).should be_nil
FraudAlert.count.should eq(0)
end
it "does not conflict if same mac, but different IP address" do
first_fingerprint2 = first_fingerprint.clone
jam_track_right.guard_against_fraud(user, first_fingerprint, remote_ip).should be_nil
MachineFingerprint.count.should eq(2)
first_fingerprint2[:all] = 'all_2'
jam_track_right_other.guard_against_fraud(other, first_fingerprint2, remote_ip2).should eq(nil)
mf = MachineFingerprint.find_by_fingerprint('all')
mf.user.should eq(user)
mf.fingerprint.should eq('all')
mf.when_taken.should eq(MachineFingerprint::TAKEN_ON_SUCCESSFUL_DOWNLOAD)
mf.print_type.should eq(MachineFingerprint::PRINT_TYPE_ALL)
mf.jam_track_right.should eq(jam_track_right)
MachineFingerprint.count.should eq(4)
FraudAlert.count.should eq(0)
end
# if you try to buy a regular jamtrack with a fingerprint belonging to another user? so what. you paid for it
it "allows re-use of fingerprint if jamtrack is a normal purchase" do
first_fingerprint2 = first_fingerprint.clone
jam_track_right.guard_against_fraud(user, first_fingerprint, remote_ip).should be_nil
MachineFingerprint.count.should eq(2)
jam_track_right_other_purchased.guard_against_fraud(other, first_fingerprint, remote_ip).should be_nil
jam_track_right_other_purchased.guard_against_fraud(other, first_fingerprint2, remote_ip).should be_nil
MachineFingerprint.count.should eq(2)
end
@ -319,13 +366,22 @@ describe JamTrackRight do
it "let's you download a free jamtrack if you have a second but undownloaded free one" do
right1 = FactoryGirl.create(:jam_track_right, user: user, redeemed: true, redeemed_and_fingerprinted: false)
first_fingerprint2 = first_fingerprint.clone
jam_track_right.guard_against_fraud(user, first_fingerprint, remote_ip).should be_nil
MachineFingerprint.count.should eq(2)
right1.guard_against_fraud(user, first_fingerprint, remote_ip).should eq('already redeemed another')
right1.guard_against_fraud(user, first_fingerprint2, remote_ip).should eq('already redeemed another')
MachineFingerprint.count.should eq(2)
end
it "creates metadata" do
right1 = FactoryGirl.create(:jam_track_right, user: user, redeemed: true, redeemed_and_fingerprinted: false)
jam_track_right.guard_against_fraud(user, full_fingerprint, remote_ip).should be_nil
MachineFingerprint.count.should eq(2)
MachineExtra.count.should eq(2)
end
end
end

View File

@ -132,6 +132,69 @@ describe Sale do
user.has_redeemable_jamtrack.should be_false
end
it "for a free jam track with an affiliate association" do
partner = FactoryGirl.create(:affiliate_partner)
user.affiliate_referral = partner
user.save!
shopping_cart = ShoppingCart.create user, jamtrack, 1, true
client.find_or_create_account(user, billing_info)
sales = Sale.place_order(user, [shopping_cart])
user.reload
user.sales.length.should eq(1)
sales.should eq(user.sales)
sale = sales[0]
sale.recurly_invoice_id.should be_nil
sale.recurly_subtotal_in_cents.should eq(0)
sale.recurly_tax_in_cents.should eq(0)
sale.recurly_total_in_cents.should eq(0)
sale.recurly_currency.should eq('USD')
sale.order_total.should eq(0)
sale.sale_line_items.length.should == 1
sale_line_item = sale.sale_line_items[0]
sale_line_item.recurly_tax_in_cents.should eq(0)
sale_line_item.recurly_total_in_cents.should eq(0)
sale_line_item.recurly_currency.should eq('USD')
sale_line_item.recurly_discount_in_cents.should eq(0)
sale_line_item.product_type.should eq(JamTrack::PRODUCT_TYPE)
sale_line_item.unit_price.should eq(jamtrack.price)
sale_line_item.quantity.should eq(1)
sale_line_item.free.should eq(1)
sale_line_item.sales_tax.should be_nil
sale_line_item.shipping_handling.should eq(0)
sale_line_item.recurly_plan_code.should eq(jamtrack.plan_code)
sale_line_item.product_id.should eq(jamtrack.id)
sale_line_item.recurly_subscription_uuid.should be_nil
sale_line_item.recurly_adjustment_uuid.should be_nil
sale_line_item.recurly_adjustment_credit_uuid.should be_nil
sale_line_item.recurly_adjustment_uuid.should eq(user.jam_track_rights.last.recurly_adjustment_uuid)
sale_line_item.recurly_adjustment_credit_uuid.should eq(user.jam_track_rights.last.recurly_adjustment_credit_uuid)
sale_line_item.affiliate_referral.should eq(partner)
sale_line_item.affiliate_referral_fee_in_cents.should eq(0)
# verify subscription is in Recurly
recurly_account = client.get_account(user)
adjustments = recurly_account.adjustments
adjustments.should_not be_nil
adjustments.should have(0).items
invoices = recurly_account.invoices
invoices.should have(0).items
# verify jam_track_rights data
user.jam_track_rights.should_not be_nil
user.jam_track_rights.should have(1).items
user.jam_track_rights.last.jam_track.id.should eq(jamtrack.id)
user.jam_track_rights.last.redeemed.should be_true
user.has_redeemable_jamtrack.should be_false
end
it "for a normally priced jam track" do
user.has_redeemable_jamtrack = false
user.save!
@ -201,6 +264,85 @@ describe Sale do
user.jam_track_rights.last.jam_track.id.should eq(jamtrack.id)
user.jam_track_rights.last.redeemed.should be_false
user.has_redeemable_jamtrack.should be_false
sale_line_item.affiliate_referral.should be_nil
sale_line_item.affiliate_referral_fee_in_cents.should be_nil
end
it "for a normally priced jam track with an affiliate association" do
user.has_redeemable_jamtrack = false
partner = FactoryGirl.create(:affiliate_partner)
user.affiliate_referral = partner
user.save!
shopping_cart = ShoppingCart.create user, jamtrack, 1, false
client.find_or_create_account(user, billing_info)
sales = Sale.place_order(user, [shopping_cart])
user.reload
user.sales.length.should eq(1)
sales.should eq(user.sales)
sale = sales[0]
sale.recurly_invoice_id.should_not be_nil
sale.recurly_subtotal_in_cents.should eq(jam_track_price_in_cents)
sale.recurly_tax_in_cents.should eq(0)
sale.recurly_total_in_cents.should eq(jam_track_price_in_cents)
sale.recurly_currency.should eq('USD')
sale.order_total.should eq(jamtrack.price)
sale.sale_line_items.length.should == 1
sale_line_item = sale.sale_line_items[0]
# validate we are storing pricing info from recurly
sale_line_item.recurly_tax_in_cents.should eq(0)
sale_line_item.recurly_total_in_cents.should eq(jam_track_price_in_cents)
sale_line_item.recurly_currency.should eq('USD')
sale_line_item.recurly_discount_in_cents.should eq(0)
sale_line_item.product_type.should eq(JamTrack::PRODUCT_TYPE)
sale_line_item.unit_price.should eq(jamtrack.price)
sale_line_item.quantity.should eq(1)
sale_line_item.free.should eq(0)
sale_line_item.sales_tax.should be_nil
sale_line_item.shipping_handling.should eq(0)
sale_line_item.recurly_plan_code.should eq(jamtrack.plan_code)
sale_line_item.product_id.should eq(jamtrack.id)
sale_line_item.recurly_subscription_uuid.should be_nil
sale_line_item.recurly_adjustment_uuid.should_not be_nil
sale_line_item.recurly_adjustment_credit_uuid.should be_nil
sale_line_item.recurly_adjustment_uuid.should eq(user.jam_track_rights.last.recurly_adjustment_uuid)
sale_line_item.affiliate_referral.should eq(partner)
sale_line_item.affiliate_referral_fee_in_cents.should eq(20)
# verify subscription is in Recurly
recurly_account = client.get_account(user)
adjustments = recurly_account.adjustments
adjustments.should_not be_nil
adjustments.should have(1).items
purchase= adjustments[0]
purchase.unit_amount_in_cents.should eq((jamtrack.price * 100).to_i)
purchase.accounting_code.should eq(ShoppingCart::PURCHASE_NORMAL)
purchase.description.should eq("JamTrack: " + jamtrack.name)
purchase.state.should eq('invoiced')
purchase.uuid.should eq(sale_line_item.recurly_adjustment_uuid)
invoices = recurly_account.invoices
invoices.should have(1).items
invoice = invoices[0]
invoice.uuid.should eq(sale.recurly_invoice_id)
invoice.line_items.should have(1).items # should have single adjustment associated
invoice.line_items[0].should eq(purchase)
invoice.subtotal_in_cents.should eq((jamtrack.price * 100).to_i)
invoice.total_in_cents.should eq((jamtrack.price * 100).to_i)
invoice.state.should eq('collected')
# verify jam_track_rights data
user.jam_track_rights.should_not be_nil
user.jam_track_rights.should have(1).items
user.jam_track_rights.last.jam_track.id.should eq(jamtrack.id)
user.jam_track_rights.last.redeemed.should be_false
user.has_redeemable_jamtrack.should be_false
end
it "for a jamtrack already owned" do

View File

@ -198,6 +198,14 @@ def app_config
true
end
def expire_fingerprint_days
14
end
def found_conflict_count
1
end
private
def audiomixer_workspace_path

View File

@ -18,6 +18,7 @@ else
gem 'jam_websockets', "0.1.#{ENV["BUILD_NUMBER"]}"
ENV['NOKOGIRI_USE_SYSTEM_LIBRARIES'] ||= "true"
end
gem 'oj', '2.10.2'
gem 'builder'
gem 'rails', '~>3.2.11'

View File

@ -3,3 +3,4 @@ Jasmine Javascript Unit Tests
Open browser to localhost:3000/teaspoon

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -8,10 +8,10 @@
var rest = context.JK.Rest();
var userId;
var user = {};
var screen = null;
var gearUtils = context.JK.GearUtilsInstance;
function beforeShow(data) {
console.log("beforeShow", data)
userId = data.id;
}
@ -71,8 +71,11 @@
invalidProfiles : invalidProfiles,
isNativeClient: gon.isNativeClient,
musician: context.JK.currentUserMusician,
webcamName: webcamName,
sales_count: userDetail.sales_count
sales_count: userDetail.sales_count,
is_affiliate_partner: userDetail.is_affiliate_partner,
affiliate_earnings: (userDetail.affiliate_earnings / 100).toFixed(2),
affiliate_referral_count: userDetail.affiliate_referral_count,
webcamName: webcamName
} , { variable: 'data' }));
$('#account-content-scroller').html($template);
@ -135,6 +138,7 @@
$('#account-content-scroller').on('avatar_changed', '#profile-avatar', function(evt, newAvatarUrl) { evt.stopPropagation(); updateAvatar(newAvatarUrl); return false; })
$("#account-content-scroller").on('click', '#account-payment-history-link', function(evt) {evt.stopPropagation(); navToPaymentHistory(); return false; } );
$("#account-content-scroller").on('click', '#account-affiliate-partner-link', function(evt) {evt.stopPropagation(); navToAffiliates(); return false; } );
}
function renderAccount() {
@ -187,6 +191,11 @@
window.location = '/client#/account/paymentHistory'
}
function navToAffiliates() {
resetForm()
window.location = '/client#/account/affiliatePartner'
}
// handle update avatar event
function updateAvatar(avatar_url) {
var photoUrl = context.JK.resolveAvatarUrl(avatar_url);
@ -203,7 +212,7 @@
}
function initialize() {
screen = $('#account-content-scroller');
var screenBindings = {
'beforeShow': beforeShow,
'afterShow': afterShow
@ -218,4 +227,4 @@
return this;
};
})(window,jQuery);
})(window,jQuery);

View File

@ -0,0 +1,324 @@
(function (context, $) {
"use strict";
context.JK = context.JK || {};
context.JK.AccountAffiliateScreen = function (app) {
var logger = context.JK.logger;
var rest = context.JK.Rest();
var userId;
var user = {};
var affiliatePartnerTabs = ['account', 'agreement', 'signups', 'earnings'];
var affiliatePartnerData = null;
var $screen = null;
function beforeShow(data) {
userId = data.id;
affiliatePartnerData = null;
}
function afterShow(data) {
rest.getAffiliatePartnerData(userId)
.done(function (response) {
affiliatePartnerData = response;
renderAffiliateTab('account')
})
.fail(app.ajaxError)
}
function events() {
// Affiliate Partner
$("#account-affiliate-partner-content-scroller").on('click', '#affiliate-partner-account-link', function (evt) {
evt.stopPropagation();
renderAffiliateTab('account');
return false;
});
$("#account-affiliate-partner-content-scroller").on('click', '#affiliate-partner-links-link', function (evt) {
evt.stopPropagation();
renderAffiliateTab('links');
return false;
});
$("#account-affiliate-partner-content-scroller").on('click', '#affiliate-partner-agreement-link', function (evt) {
evt.stopPropagation();
renderAffiliateTab('agreement');
return false;
});
$("#account-affiliate-partner-content-scroller").on('click', '#affiliate-partner-signups-link', function (evt) {
evt.stopPropagation();
renderAffiliateTab('signups');
return false;
});
$("#account-affiliate-partner-content-scroller").on('click', '#affiliate-partner-earnings-link', function (evt) {
evt.stopPropagation();
renderAffiliateTab('earnings');
return false;
});
$("#account-affiliate-partner-content-scroller").on('click', '#affiliate-profile-account-submit', function (evt) {
evt.stopPropagation();
handleUpdateAffiliateAccount();
return false;
});
}
function _renderAffiliateTableSignups(rows) {
rest.getAffiliateSignups()
.done(onAffiliateSignups)
.fail(app.ajaxError)
}
function _renderAffiliateTableEarnings(rows) {
rest.getAffiliatePayments()
.done(onAffiliatePayments)
.fail(app.ajaxError)
}
function _renderAffiliateTableLinks(rows) {
$screen.find('.affiliate-agreement').on('click', function () {
renderAffiliateTab('agreement');
return false;
})
$screen.find('.affiliate-link-page').attr('href', '/affiliate/links/' + affiliatePartnerData.account.id)
$screen.find('select.link_type').easyDropDown();
var $linkType = $screen.find('.link_type')
$linkType.on('change', function() {
logger.debug("link type changed")
updateLinks();
})
updateLinks();
}
function onAffiliateSignups(signups) {
var $table = $screen.find('table.traffic-table tbody')
$table.empty();
var template = $('#template-affiliate-partner-signups-row').html();
context._.each(signups.traffics, function(item) {
var $link = $(context._.template(template, item, {variable: 'data'}));
var $day = $link.find('td.day')
var day = $day.text();
var bits = day.split('-')
if(bits.length == 3) {
$day.text(context.JK.getMonth(new Number(bits[1]) - 1) + ' ' + new Number(bits[2]))
}
$table.append($link)
})
}
function onAffiliatePayments(payments) {
var $table = $screen.find('table.payment-table tbody')
$table.empty();
var template = $('#template-affiliate-partner-earnings-row').html();
context._.each(payments.payments, function(item) {
var data = {}
if(item.payment_type == 'quarterly') {
if(item.quarter == 0) {
data.time = '1st Quarter ' + item.year
}
else if(item.quarter == 1) {
data.time = '2nd Quarter ' + item.year
}
else if(item.quarter == 2) {
data.time = '3rd Quarter ' + item.year
}
else if(item.quarter == 3) {
data.time = '4th Quarter ' + item.year
}
data.sold = ''
if(item.paid) {
data.earnings = 'PAID $' + (item.due_amount_in_cents / 100).toFixed(2);
}
else {
data.earnings = 'No earning were paid, as the $10 minimum threshold was not reached.'
}
}
else {
data.time = context.JK.getMonth(item.month - 1) + ' ' + item.year;
if(item.jamtracks_sold == 1) {
data.sold = 'JamTracks: ' + item.jamtracks_sold + ' unit sold';
}
else {
data.sold = 'JamTracks: ' + item.jamtracks_sold + ' units sold';
}
data.earnings = '$' + (item.due_amount_in_cents / 100).toFixed(2);
}
var $earning = $(context._.template(template, data, {variable: 'data'}));
$table.append($earning)
})
}
function updateLinks() {
var $select = $screen.find('select.link_type')
var value = $select.val()
logger.debug("value: " + value)
var type = 'jamtrack_songs';
if(value == 'JamTrack Song') {
type = 'jamtrack_songs'
}
else if(value == 'JamTrack Band') {
type = 'jamtrack_bands'
}
else if(value == 'JamTrack General') {
type = 'jamtrack_general'
}
else if(value == 'JamKazam General') {
type = 'jamkazam'
}
else if(value == 'JamKazam Session') {
type = 'sessions'
}
else if(value == 'JamKazam Recording') {
type = 'recordings'
}
else if(value == 'Custom Link') {
type = 'custom_links'
}
$screen.find('.link-type-prompt').hide();
$screen.find('.link-type-prompt[data-type="' + type + '"]').show();
if(type == 'custom_links') {
$screen.find('table.links-table').hide();
$screen.find('.link-type-prompt[data-type="custom_links"] span.affiliate_id').text(affiliatePartnerData.account.id)
}
else {
rest.getLinks(type)
.done(populateLinkTable)
.fail(function() {
app.notify({message: 'Unable to fetch links. Please try again later.' })
})
}
}
function _renderAffiliateTab(theTab) {
affiliateTabRefresh(theTab);
var template = $('#template-affiliate-partner-' + theTab).html();
var tabHtml = context._.template(template, affiliatePartnerData[theTab], {variable: 'data'});
$('#affiliate-partner-tab-content').html(tabHtml);
if (theTab == 'signups') {
_renderAffiliateTableSignups(affiliatePartnerData[theTab]);
} else if (theTab == 'earnings') {
_renderAffiliateTableEarnings(affiliatePartnerData[theTab]);
} else if (theTab == 'links') {
_renderAffiliateTableLinks(affiliatePartnerData[theTab]);
}
}
function renderAffiliateTab(theTab) {
if (affiliatePartnerData) {
return _renderAffiliateTab(theTab);
}
rest.getAffiliatePartnerData(userId)
.done(function (response) {
affiliatePartnerData = response;
_renderAffiliateTab(theTab);
})
.fail(app.ajaxError)
}
function affiliateTabRefresh(selectedTab) {
var container = $('#account-affiliate-partner-content-scroller');
container.find('.affiliate-partner-nav a.active').removeClass('active');
if (selectedTab) {
container.find('.affiliate-partner-' + selectedTab).show();
$.each(affiliatePartnerTabs, function (index, val) {
if (val != selectedTab) {
container.find('.affiliate-partner-' + val).hide();
}
});
container.find('.affiliate-partner-nav a#affiliate-partner-' + selectedTab + '-link').addClass('active');
} else {
$.each(affiliatePartnerTabs, function (index, val) {
container.find('.affiliate-partner-' + val).hide();
});
container.find('.affiliate-partner-nav a#affiliate-partner-' + affiliatePartnerTabs[0] + '-link').addClass('active');
}
}
function handleUpdateAffiliateAccount() {
var tab_content = $('#affiliate-partner-tab-content');
var affiliate_partner_data = {
'address': {
'address1': tab_content.find('#affiliate_partner_address1').val(),
'address2': tab_content.find('#affiliate_partner_address2').val(),
'city': tab_content.find('#affiliate_partner_city').val(),
'state': tab_content.find('#affiliate_partner_state').val(),
'postal_code': tab_content.find('#affiliate_partner_postal_code').val(),
'country': tab_content.find('#affiliate_partner_country').val()
},
'tax_identifier': tab_content.find('#affiliate_partner_tax_identifier').val()
}
rest.postAffiliatePartnerData(userId, affiliate_partner_data)
.done(postUpdateAffiliateAccountSuccess);
}
function postUpdateAffiliateAccountSuccess(response) {
app.notify(
{
title: "Affiliate Account",
text: "You have updated your affiliate partner data successfully."
},
null, true);
}
function populateLinkTable(response) {
$screen.find('table.links-table').show();
var $linkTable = $screen.find('.links-table tbody')
$linkTable.empty();
var template = $('#template-affiliate-link-entry').html();
context._.each(response, function(item) {
var $link = $(context._.template(template, item, {variable: 'data'}));
$link.find('td.copy-link a').click(copyLink)
$linkTable.append($link)
})
}
function copyLink() {
var element = $(this);
var $url = element.closest('tr').find('td.url input')
$url.select()
return false;
}
function initialize() {
var screenBindings = {
'beforeShow': beforeShow,
'afterShow': afterShow
};
app.bindScreen('account/affiliatePartner', screenBindings);
$screen = $('#account-affiliate-partner')
events();
}
this.initialize = initialize;
this.beforeShow = beforeShow;
this.afterShow = afterShow;
return this;
};
})(window, jQuery);

View File

@ -3,8 +3,11 @@
"use strict";
context.JK = context.JK || {};
context.JK.AccountProfileSamples = function(app, parent) {
var $document = $(document);
// TODO: Add a target type, which can be band or user -- call the
// appropriate API methods.
context.JK.AccountProfileSamples = function(app, parent, loadFn, updateFn) {
var $document = $(document)
// used to initialize RecordingSourceValidator in site_validator.js.coffee
window.jamkazamRecordingSources = [];
@ -17,18 +20,16 @@
var ui = new context.JK.UIHelper(JK.app);
var target = {};
var profileUtils = context.JK.ProfileUtils;
var $screen = $('.account-profile-samples', parent);
var $screen = $('.profile-online-sample-controls', parent);
// online presences
var $website = $screen.find('#website');
var $soundCloudUsername = $screen.find('#soundcloud-username');
var $reverbNationUsername = $screen.find('#reverbnation-username');
var $bandCampUsername = $screen.find('#bandcamp-username');
var $fandalismUsername = $screen.find('#fandalism-username');
var $youTubeUsername = $screen.find('#youtube-username');
var $facebookUsername = $screen.find('#facebook-username');
var $twitterUsername = $screen.find('#twitter-username');
var $website = $screen.find('.website');
var $soundCloudUsername = $screen.find('.soundcloud-username');
var $reverbNationUsername = $screen.find('.reverbnation-username');
var $bandCampUsername = $screen.find('.bandcamp-username');
var $fandalismUsername = $screen.find('.fandalism-username');
var $youTubeUsername = $screen.find('.youtube-username');
var $facebookUsername = $screen.find('.facebook-username');
var $twitterUsername = $screen.find('.twitter-username');
// performance samples
var $jamkazamSampleList = $screen.find('.samples.jamkazam').find('.sample-list');
@ -36,80 +37,85 @@
var $youTubeSampleList = $screen.find('.samples.youtube').find('.sample-list');
// buttons
var $btnAddJkRecording = $screen.find('#btn-add-jk-recording');
var $btnCancel = $screen.find('#account-edit-profile-cancel');
var $btnBack = $screen.find('#account-edit-profile-back');
var $btnSubmit = $screen.find('#account-edit-profile-submit');
var $btnAddJkRecording = $screen.find('.btn-add-jk-recording');
var $btnCancel = $screen.find('.account-edit-profile-cancel');
var $btnBack = $screen.find('.account-edit-profile-back');
var $btnSubmit = $screen.find('.account-edit-profile-submit');
function beforeShow(data) {
}
function afterShow(data) {
$.when(api.getUserProfile())
.done(function(userDetail) {
renderPresence(userDetail);
renderSamples(userDetail);
$document.triggerHandler('INIT_SITE_VALIDATORS');
function afterShow(data) {
$.when(loadFn())
.done(function(targetPlayer) {
if (targetPlayer && targetPlayer.keys && targetPlayer.keys.length > 0) {
renderPlayer(targetPlayer)
}
});
}
function renderPresence(user) {
$website.val(user.website);
function renderPlayer(targetPlayer) {
renderPresence(targetPlayer);
renderSamples(targetPlayer);
}
function renderPresence(targetPlayer) {
$website.val(targetPlayer.website);
// SoundCloud
var presences = profileUtils.soundCloudPresences(user.online_presences);
var presences = profileUtils.soundCloudPresences(targetPlayer.online_presences);
if (presences && presences.length > 0) {
$soundCloudUsername.val(presences[0].username);
}
// ReverbNation
presences = profileUtils.reverbNationPresences(user.online_presences);
presences = profileUtils.reverbNationPresences(targetPlayer.online_presences);
if (presences && presences.length > 0) {
$reverbNationUsername.val(presences[0].username);
}
// Bandcamp
presences = profileUtils.bandCampPresences(user.online_presences);
presences = profileUtils.bandCampPresences(targetPlayer.online_presences);
if (presences && presences.length > 0) {
$bandCampUsername.val(presences[0].username);
}
// Fandalism
presences = profileUtils.fandalismPresences(user.online_presences);
presences = profileUtils.fandalismPresences(targetPlayer.online_presences);
if (presences && presences.length > 0) {
$fandalismUsername.val(presences[0].username);
}
// YouTube
presences = profileUtils.youTubePresences(user.online_presences);
presences = profileUtils.youTubePresences(targetPlayer.online_presences);
if (presences && presences.length > 0) {
$youTubeUsername.val(presences[0].username);
}
// Facebook
presences = profileUtils.facebookPresences(user.online_presences);
presences = profileUtils.facebookPresences(targetPlayer.online_presences);
if (presences && presences.length > 0) {
$facebookUsername.val(presences[0].username);
}
// Twitter
presences = profileUtils.twitterPresences(user.online_presences);
presences = profileUtils.twitterPresences(targetPlayer.online_presences);
if (presences && presences.length > 0) {
$twitterUsername.val(presences[0].username);
}
}
function renderSamples(user) {
function renderSamples(targetPlayer) {
// JamKazam recordings
var samples = profileUtils.jamkazamSamples(user.performance_samples);
var samples = profileUtils.jamkazamSamples(targetPlayer.performance_samples);
loadSamples(samples, 'jamkazam', $jamkazamSampleList, window.jamkazamRecordingSources);
// SoundCloud recordings
samples = profileUtils.soundCloudSamples(user.performance_samples);
samples = profileUtils.soundCloudSamples(targetPlayer.performance_samples);
loadSamples(samples, 'soundcloud', $soundCloudSampleList, window.soundCloudRecordingSources);
// YouTube videos
samples = profileUtils.youTubeSamples(user.performance_samples);
samples = profileUtils.youTubeSamples(targetPlayer.performance_samples);
loadSamples(samples, 'youtube', $youTubeSampleList, window.youTubeRecordingSources);
}
@ -270,9 +276,22 @@
function handleUpdateProfile() {
disableSubmits()
var player = buildPlayer()
updateFn({
website: player.website,
online_presences: player.online_presences,
performance_samples: player.performance_samples
})
.done(postUpdateProfileSuccess)
.fail(postUpdateProfileFailure)
.always(enableSubmits);
}
function buildPlayer() {
// extract online presences
var op = [];
var presenceTypes = profileUtils.ONLINE_PRESENCE_TYPES;
var presenceTypes = profileUtils.ONLINE_PRESENCE_TYPES;
addOnlinePresence(op, $soundCloudUsername.val(), presenceTypes.SOUNDCLOUD.description);
addOnlinePresence(op, $reverbNationUsername.val(), presenceTypes.REVERBNATION.description);
addOnlinePresence(op, $bandCampUsername.val(), presenceTypes.BANDCAMP.description);
@ -287,15 +306,12 @@
addPerformanceSamples(ps, $jamkazamSampleList, performanceSampleTypes.JAMKAZAM.description);
addPerformanceSamples(ps, $soundCloudSampleList, performanceSampleTypes.SOUNDCLOUD.description);
addPerformanceSamples(ps, $youTubeSampleList, performanceSampleTypes.YOUTUBE.description);
api.updateUser({
return {
website: $website.val(),
online_presences: op,
performance_samples: ps
})
.done(postUpdateProfileSuccess)
.fail(postUpdateProfileFailure)
.always(enableSubmits);
}
}
function postUpdateProfileSuccess(response) {
@ -333,54 +349,51 @@
// This function is a bit of a mess. It was pulled
// from the html.erb file verbatim, and could use a
// refactor:
function initializeValidators() {
function initializeValidators() {
var initialized = false;
$(document).on('INIT_SITE_VALIDATORS', function(e, data) {
window.soundCloudRecordingValidator.init(window.soundCloudRecordingSources);
window.youTubeRecordingValidator.init(window.youTubeRecordingSources);
});
$(document).on('JAMKAZAM_READY', function(e, data) {
//$document.on('JAMKAZAM_READY', function(e, data) {
window.JK.JamServer.get$Server().on(window.JK.EVENTS.CONNECTION_UP, function() {
if(initialized) {
if(initialized) {
return;
}
initialized = true;
//var $screen = $('#account-profile-samples');
var $btnAddSoundCloudRecording = $screen.find('#btn-add-soundcloud-recording');
var $btnAddYouTubeVideo = $screen.find('#btn-add-youtube-video');
var $btnAddSoundCloudRecording = $screen.find('.btn-add-soundcloud-recording');
var $btnAddYouTubeVideo = $screen.find('.btn-add-youtube-video');
var $soundCloudSampleList = $screen.find('.samples.soundcloud');
var $youTubeSampleList = $screen.find('.samples.youtube');
setTimeout(function() {
window.urlValidator = new JK.SiteValidator('url', userNameSuccessCallback, userNameFailCallback);
setTimeout(function() {
window.urlValidator = new JK.SiteValidator('url', userNameSuccessCallback, userNameFailCallback, parent);
window.urlValidator.init();
window.soundCloudValidator = new JK.SiteValidator('soundcloud', userNameSuccessCallback, userNameFailCallback);
window.soundCloudValidator = new JK.SiteValidator('soundcloud', userNameSuccessCallback, userNameFailCallback, parent);
window.soundCloudValidator.init();
window.reverbNationValidator = new JK.SiteValidator('reverbnation', userNameSuccessCallback, userNameFailCallback);
window.reverbNationValidator = new JK.SiteValidator('reverbnation', userNameSuccessCallback, userNameFailCallback, parent);
window.reverbNationValidator.init();
window.bandCampValidator = new JK.SiteValidator('bandcamp', userNameSuccessCallback, userNameFailCallback);
window.bandCampValidator = new JK.SiteValidator('bandcamp', userNameSuccessCallback, userNameFailCallback, parent);
window.bandCampValidator.init();
window.fandalismValidator = new JK.SiteValidator('fandalism', userNameSuccessCallback, userNameFailCallback);
window.fandalismValidator = new JK.SiteValidator('fandalism', userNameSuccessCallback, userNameFailCallback, parent);
window.fandalismValidator.init();
window.youTubeValidator = new JK.SiteValidator('youtube', userNameSuccessCallback, userNameFailCallback);
window.youTubeValidator = new JK.SiteValidator('youtube', userNameSuccessCallback, userNameFailCallback, parent);
window.youTubeValidator.init();
window.facebookValidator = new JK.SiteValidator('facebook', userNameSuccessCallback, userNameFailCallback);
window.facebookValidator = new JK.SiteValidator('facebook', userNameSuccessCallback, userNameFailCallback, parent);
window.facebookValidator.init();
window.twitterValidator = new JK.SiteValidator('twitter', userNameSuccessCallback, userNameFailCallback);
window.twitterValidator = new JK.SiteValidator('twitter', userNameSuccessCallback, userNameFailCallback, parent);
window.twitterValidator.init();
window.soundCloudRecordingValidator = new JK.RecordingSourceValidator('rec_soundcloud', soundCloudSuccessCallback, siteFailCallback);
window.soundCloudRecordingValidator = new JK.RecordingSourceValidator('rec_soundcloud', soundCloudSuccessCallback, siteFailCallback, parent);
logger.debug("window.soundCloudRecordingValidator", window.soundCloudRecordingValidator)
window.youTubeRecordingValidator = new JK.RecordingSourceValidator('rec_youtube', youTubeSuccessCallback, siteFailCallback, parent);
window.youTubeRecordingValidator = new JK.RecordingSourceValidator('rec_youtube', youTubeSuccessCallback, siteFailCallback);
$document.triggerHandler('INIT_SITE_VALIDATORS');
}, 1);
@ -389,7 +402,7 @@
$inputDiv.find('.error-text').remove();
}
function userNameFailCallback($inputDiv) {
function userNameFailCallback($inputDiv) {
$inputDiv.addClass('error');
$inputDiv.find('.error-text').remove();
$inputDiv.append("<span class='error-text'>Invalid username</span>").show();
@ -432,9 +445,21 @@
$inputDiv.append("<span class='error-text'>Invalid URL</span>").show();
}
});
//});
$document.on('INIT_SITE_VALIDATORS', function(e, data) {
logger.debug("ZZZwindow.soundCloudRecordingValidator", window.soundCloudRecordingValidator)
window.soundCloudRecordingValidator.init(window.soundCloudRecordingSources);
window.youTubeRecordingValidator.init(window.youTubeRecordingSources);
});
} // end initializeValidators.
function resetForm() {
$("input", $screen).val("")
}
function initialize() {
var screenBindings = {
'beforeShow': beforeShow,
@ -449,6 +474,9 @@
this.initialize = initialize;
this.beforeShow = beforeShow;
this.afterShow = afterShow;
this.buildPlayer = buildPlayer;
this.renderPlayer = renderPlayer
this.resetForm = resetForm;
return this;
};
})(window,jQuery);

View File

@ -10,6 +10,7 @@
// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
// GO AFTER THE REQUIRES BELOW.
//
//= require bugsnag
//= require bind-polyfill
//= require jquery
//= require jquery.monkeypatch
@ -36,6 +37,7 @@
//= require jquery.custom-protocol
//= require jquery.exists
//= require jquery.payment
//= require jquery.visible
//= require howler.core.js
//= require jstz
//= require class

View File

@ -10,32 +10,37 @@
context.JK.BandSetupScreen = function (app) {
var NONE_SPECIFIED = 'None specified'
var GENRE_STEP = 1
var GENRE_STEP = 1
var SAMPLE_STEP = 3
var STEPS_COUNT = 5
var currentStep = 0
var ui = new context.JK.UIHelper(JK.app)
var logger = context.JK.logger;
var profileUtils = context.JK.ProfileUtils;
var rest = context.JK.Rest();
var inviteMusiciansUtil = null;
var invitationDialog = null;
var autoComplete = null;
var userNames = [];
var userIds = [];
var userPhotoUrls = [];
var selectedFriendIds = {};
var nilOptionStr = '<option value=""></option>';
var nilOptionText = 'n/a';
var bandId = '';
var friendInput=null;
var bandType=null;
var bandStatus=null;
var concertCount=null;
var currentStep = 0;
var STEPS_COUNT=5;
var logger = context.JK.logger
var profileUtils = context.JK.ProfileUtils
var rest = context.JK.Rest()
var inviteMusiciansUtil = null
var invitationDialog = null
var autoComplete = null
var userNames = []
var userIds = []
var userPhotoUrls = []
var selectedFriendIds = {}
var nilOptionStr = '<option value=""></option>'
var nilOptionText = 'n/a'
var bandId = ''
var friendInput=null
var bandType=null
var bandStatus=null
var concertCount=null
var $screen=$("#band-setup")
var $samples = $screen.find(".account-profile-samples")
var $selectedInstruments=[]
var accountProfileSamples = null;
var accountProfileSamples = new JK.AccountProfileSamples(app, $screen, loadBandCallback, rest.updateBand)
accountProfileSamples.initialize()
function navBack() {
if (currentStep>0) {
saveBand(function() {
@ -46,7 +51,7 @@
}
function navCancel() {
resetForm()
resetForm()
window.history.go(-1)
return false
}
@ -72,6 +77,9 @@
$("#btn-band-setup-back").addClass("hidden")
$("#btn-band-setup-next").removeClass("hidden").html("SAVE & NEXT")
} else if(currentStep<STEPS_COUNT-1) {
// if(currentStep==SAMPLE_STEP) {
// accountProfileSamples.renderPlayer(band)
// }
$("#btn-band-setup-back").removeClass("hidden")
$("#btn-band-setup-next").removeClass("hidden").html("SAVE & NEXT")
} else {
@ -113,6 +121,7 @@
function resetForm() {
removeErrors();
accountProfileSamples.resetForm()
// name
$("#band-name").val('');
@ -234,9 +243,37 @@
band.instruments.push(h)
})
if(!isNewBand()) {
mergePerformanceSamples(band)
}
return band;
}
function mergePerformanceSamples(band) {
// Collect and merge data from this sub-widget:
var performanceSampleData = accountProfileSamples.buildPlayer()
band.website=performanceSampleData.website
band.online_presences=performanceSampleData.online_presences
band.performance_samples=performanceSampleData.performance_samples
// Change player id to that of band. Widget currently hardwires current user id:
if(band.online_presences) {
for (var i=0; i<band.online_presences.length; ++i) {
band.online_presences[i].player_id = band.id
}
}
// Change player id to that of band. Widget currently hardwires current user id:
if(band.performance_samples) {
for (var i=0; i<band.performance_samples.length; ++i) {
band.performance_samples[i].player_id = band.id
}
}
return band
}
function renderDesiredExperienceLabel(selectedInstruments) {
$selectedInstruments=selectedInstruments
var instrumentText=""
@ -260,15 +297,18 @@
}
}
function saveBand(saveFn) {
function saveBand(saveBandSuccessFn) {
unbindNavButtons()
var band = buildBand()
var band = buildBand()
var saveBandFn = (isNewBand()) ? rest.createBand : rest.updateBand
saveBandFn(band)
.done(function (response) {
.done(function (response) {
bandId = response.id
saveInvitations()
saveFn(band)
if(saveBandSuccessFn) {
saveBandSuccessFn(band)
}
})
.fail(function (jqXHR) {
if(jqXHR.status == 422) {
@ -317,7 +357,7 @@
currentStep=stepNum
delete data['d'];
}
}
}
resetForm();
}
@ -410,6 +450,8 @@
renderDesiredExperienceLabel($selectedInstruments)
accountProfileSamples.renderPlayer(band)
});
}
@ -654,13 +696,14 @@
return false
}
function loadBandCallback() {
return (isNewBand()) ? {} : rest.getBand(bandId)
}
function initialize(invitationDialogInstance, friendSelectorDialog) {
inviteMusiciansUtil = new JK.InviteMusiciansUtil(app)
inviteMusiciansUtil.initialize(friendSelectorDialog)
accountProfileSamples = new JK.AccountProfileSamples(JK.app, $screen)
accountProfileSamples.initialize()
friendInput = inviteMusiciansUtil.inviteBandCreate('#band-setup-invite-musicians', "<div class='left w70'>If your bandmates are already on JamKazam, start typing their names in the box below, or click the Choose Friends button to select them.</div>")
invitationDialog = invitationDialogInstance
events()
@ -676,7 +719,7 @@
app.bindScreen('band/setup', screenBindings)
$('input[type=radio]').iCheck({
$screen.find('input[type=radio]').iCheck({
checkboxClass: 'icheckbox_minimal',
radioClass: 'iradio_minimal',
inheritClass: true

View File

@ -185,7 +185,7 @@
displayTax(effectiveQuantity, unitTax, 1.99 + unitTax)
}
else {
checkoutUtils.configureRecurly()
var pricing = context.recurly.Pricing();
pricing.plan_code = gon.recurly_tax_estimate_jam_track_plan;
pricing.resolved = false;

View File

@ -8,6 +8,7 @@ class CheckoutUtils
@rest = new context.JK.Rest();
@cookie_name = "preserve_billing"
@lastPurchaseResponse = null
@configuredRecurly = false
init: () =>
@ -54,5 +55,10 @@ class CheckoutUtils
return carts[0].product_info.free
configureRecurly: () =>
unless @configuredRecurly
context.recurly.configure(gon.global.recurly_public_api_key)
@configuredRecurly = true
# global instance
context.JK.CheckoutUtilsInstance = new CheckoutUtils()

View File

@ -1,3 +1,4 @@
//= require bugsnag
//= require jquery
//= require jquery.queryparams
//= require AAA_Log

View File

@ -133,20 +133,25 @@
if(options.buttons) {
context._.each(options.buttons, function(button, i) {
if(!button.name) throw "button.name must be specified";
if(!button.click) throw "button.click must be specified";
if(!button.click && !button.href) throw "button.click or button.href must be specified";
var buttonStyle = button.buttonStyle;
if(!buttonStyle) {
buttonStyle = options.buttons.length == i + 1 ? 'button-orange' : 'button-grey';
}
var $btn = $('<a class="' + buttonStyle + ' user-btn">' + button.name + '</a>');
$btn.click(function() {
button.click();
hide();
return false;
});
if(button.href) {
$btn.attr('href', button.href)
}
else {
$btn.click(function() {
button.click();
hide();
return false;
});
}
$buttons.append($btn);
});
}

View File

@ -20,6 +20,11 @@
var stun = null;
var rest = context.JK.Rest();
if(gon.global.web_performance_timing_enabled) {
$(window).on('load', sendTimingResults)
$(window).on('pagehide', setNavigationStart)
}
$(document).on('JAMKAZAM_CONSTRUCTED', function(e, data) {
@ -245,4 +250,63 @@
}
}
function setNavigationStart() {
if(!window.performance || !window.performance.timing) {
try {
window.sessionStorage.setItem('navigationStart', Date.now())
}
catch(e) {
logger.debug("unable to accesss sessionStorage")
}
}
}
// http://githubengineering.com/browser-monitoring-for-github-com/
function sendTimingResults() {
setTimeout(function() {
var timing;
var hasTimingApi;
if(window.performance && window.performance.timing) {
timing = window.performance.timing
hasTimingApi = true;
}
else {
timing = {}
hasTimingApi = false;
}
// Merge in simulated cross-browser load event
timing['crossBrowserLoadEvent'] = Date.now()
var chromeFirstPaintTime;
if(window.chrome && window.chrome.loadTimes) {
var loadTimes = window.chrome.loadTimes()
if(loadTimes) {
chromeFirstPaintTime = true;
}
}
// firstPaintTime is in seconds; convert to milliseconds to match performance.timing.
timing['chromeFirstPaintTime'] = Math.round(chromeFirstPaintTime * 1000);
// Merge in simulated navigation start, if no navigation timing
if (!hasTimingApi) {
try {
var navStart = window.sessionStorage.getItem('navigationStart')
if(navStart) {
timing['navigationStart'] = parseInt(navStart, 10)
}
}
catch(e) { }
}
context.JK.GA.trackTiming(timing);
}, 0)
}
})(window, jQuery);

View File

@ -284,6 +284,37 @@
logger.debug("Tracked Jam Track Play")
}
function trackTiming(timing) {
if(!timing) {return}
try {
var computed = {
dns: timing.domainLookupEnd - timing.domainLookupStart,
connect: timing.connectEnd - timing.connectStart,
ttfb: timing.responseStart - timing.connectEnd,
basePage: timing.responseEnd - timing.responseStart,
frontEnd: timing.loadEventStart - timing.responseEnd,
domContentLoadedEvent: timing.domContentLoadedEventEnd - timing.domContentLoadedEventStart,
windowLoadEvent: timing.loadEventEnd - timing.loadEventStart,
domInteractive: timing.domInteractive - timing.domLoading,
domComplete: timing.domComplete - timing.domLoading,
domCompleteToOnload: timing.loadEventStart - timing.domComplete
};
logger.debug("page load time: " + computed.frontEnd)
context._.each(computed, function (value, key) {
if (value > 0 && value < 60000) {
context.ga("send", "timing", "NavigationTiming", key, value, null, {'page' : '/' + window.location.pathname});
}
})
//context.stats.write('web.timing.navigation', computed)
}
catch(e) {
logger.error("loading times failed in ga.js", e)
}
}
// if you want to pass in no title, either omit it from the arg list when u invoke virtualPageView, or pass in undefined, NOT null
function virtualPageView(page, title) {
@ -445,6 +476,7 @@
GA.trackBand = trackBand;
GA.trackJKSocial = trackJKSocial;
GA.virtualPageView = virtualPageView;
GA.trackTiming = trackTiming;
context.JK.GA = GA;

View File

@ -52,7 +52,8 @@
JAMTRACK_DOWNLOADER_STATE_CHANGED: 'jamtrack_downloader_state',
METRONOME_PLAYBACK_MODE_SELECTED: 'metronome_playback_mode_selected',
CHECKOUT_SIGNED_IN: 'checkout_signed_in',
CHECKOUT_SKIP_SIGN_IN: 'checkout_skip_sign_in'
CHECKOUT_SKIP_SIGN_IN: 'checkout_skip_sign_in',
PREVIEW_PLAYED: 'preview_played'
};
context.JK.PLAYBACK_MONITOR_MODE = {

View File

@ -62,6 +62,24 @@
}
}
helpBubble.rotateJamTrackLandingBubbles = function($preview, $video, $ctaButton, $alternativeCta) {
$(window).on('load', function() {
setTimeout(function() {
helpBubble.jamtrackLandingPreview($preview, $preview.offsetParent())
setTimeout(function() {
helpBubble.jamtrackLandingVideo($video, $video.offsetParent())
setTimeout(function() {
helpBubble.jamtrackLandingCta($ctaButton, $alternativeCta)
}, 11000); // 5 seconds on top of 6 second show time of bubbles
}, 11000); // 5 seconds on top of 6 second show time of bubbles
}, 1500)
})
}
helpBubble.clearJamTrackGuide = clearJamTrackGuideTimeout;
helpBubble.jamtrackGuideTile = function ($element, $offsetParent) {
@ -82,4 +100,33 @@
})
}
helpBubble.jamtrackLandingPreview = function($element, $offsetParent) {
context.JK.prodBubble($element, 'jamtrack-landing-preview', {}, bigHelpOptions({positions:['right'], offsetParent: $offsetParent}))
}
helpBubble.jamtrackLandingVideo = function($element, $offsetParent) {
context.JK.prodBubble($element, 'jamtrack-landing-video', {}, bigHelpOptions({positions:['left'], offsetParent: $offsetParent}))
}
helpBubble.jamtrackLandingCta = function($element, $alternativeElement) {
if ($element.visible()) {
context.JK.prodBubble($element, 'jamtrack-landing-cta', {}, bigHelpOptions({positions:['left']}))
}
else {
context.JK.prodBubble($alternativeElement, 'jamtrack-landing-cta', {}, bigHelpOptions({positions:['right']}))
}
}
helpBubble.jamtrackBrowseBand = function($element, $offsetParent) {
return context.JK.prodBubble($element, 'jamtrack-browse-band', {}, bigHelpOptions({positions:['top'], offsetParent: $offsetParent}))
}
helpBubble.jamtrackBrowseMasterMix = function($element, $offsetParent) {
return context.JK.prodBubble($element, 'jamtrack-browse-master-mix', {}, bigHelpOptions({positions:['top'], offsetParent: $offsetParent}))
}
helpBubble.jamtrackBrowseCta = function($element, $offsetParent) {
return context.JK.prodBubble($element, 'jamtrack-browse-cta', {}, bigHelpOptions({positions:['top'], offsetParent: $offsetParent}))
}
})(window, jQuery);

View File

@ -499,6 +499,80 @@
return profile;
}
function createAffiliatePartner(options) {
return $.ajax({
type: "POST",
url: '/api/affiliate_partners',
dataType: "json",
contentType: 'application/json',
data: JSON.stringify(options)
})
}
function getAffiliatePartnerData(userId) {
return $.ajax({
type: "GET",
dataType: "json",
url: "/api/users/"+userId+"/affiliate_partner"
});
}
function postAffiliatePartnerData(userId, data) {
return $.ajax({
type: "POST",
dataType: "json",
url: "/api/users/"+userId+"/affiliate_partner",
contentType: 'application/json',
processData:false,
data: JSON.stringify(data)
});
}
function getLinks(type, partner_id) {
var url = "/api/links/" + type;
if(partner_id) {
url += '?affiliate_id=' + partner_id;
}
return $.ajax({
type: "GET",
dataType: "json",
url: url
});
}
function getAffiliateSignups() {
return $.ajax({
type: "GET",
dataType: "json",
url: "/api/affiliate_partners/signups"
});
}
function getAffiliateMonthly() {
return $.ajax({
type: "GET",
dataType: "json",
url: "/api/affiliate_partners/monthly_earnings"
});
}
function getAffiliateQuarterly() {
return $.ajax({
type: "GET",
dataType: "json",
url: "/api/affiliate_partners/quarterly_earnings"
});
}
function getAffiliatePayments() {
return $.ajax({
type: "GET",
dataType: "json",
url: "/api/affiliate_partners/payments"
});
}
function getCities(options) {
var country = options['country']
var region = options['region']
@ -1492,12 +1566,13 @@
function enqueueJamTrack(options) {
var jamTrackId = options['id'];
delete options['id']
return $.ajax({
type: "POST",
url: '/api/jamtracks/enqueue/' + jamTrackId + '?' + $.param(options),
url: '/api/jamtracks/enqueue/' + jamTrackId,
dataType: "json",
contentType: 'applications/json'
data: options
});
}
@ -1751,6 +1826,14 @@
this.updateScheduledSession = updateScheduledSession;
this.getUserDetail = getUserDetail;
this.getUserProfile = getUserProfile;
this.getAffiliatePartnerData = getAffiliatePartnerData;
this.postAffiliatePartnerData = postAffiliatePartnerData;
this.createAffiliatePartner = createAffiliatePartner;
this.getLinks = getLinks;
this.getAffiliateSignups = getAffiliateSignups;
this.getAffiliateMonthly = getAffiliateMonthly;
this.getAffiliateQuarterly = getAffiliateQuarterly;
this.getAffiliatePayments = getAffiliatePayments;
this.getCities = getCities;
this.getRegions = getRegions;
this.getCountries = getCountries;

View File

@ -9,7 +9,7 @@ context.JK.JamTrackPreview = class JamTrackPreview
@EVENTS = context.JK.EVENTS
@rest = context.JK.Rest()
@logger = context.JK.logger
@options = options || {master_shows_duration: false, color:'gray', add_line_break: false}
@options = options || {master_shows_duration: false, color:'gray', add_line_break: false, preload_master:false}
@app = app
@jamTrack = jamTrack
@jamTrackTrack = jamTrackTrack
@ -19,7 +19,9 @@ context.JK.JamTrackPreview = class JamTrackPreview
@instrumentIcon = null
@instrumentName = null
@part = null
@loaded = false
@loading = null
@loadingText = null
template = $('#template-jam-track-preview')
throw "no jam track preview template" if not template.exists()
@ -31,6 +33,7 @@ context.JK.JamTrackPreview = class JamTrackPreview
@instrumentName = @root.find('.instrument-name')
@part = @root.find('.part')
@loading = @root.find('.loading')
@loadingText = @root.find('.loading-text')
@playButton.on('click', @play)
@stopButton.on('click', @stop)
@ -91,6 +94,16 @@ context.JK.JamTrackPreview = class JamTrackPreview
if @no_audio
@playButton.addClass('disabled')
@stopButton.addClass('disabled')
else
if @options.preload_master && @jamTrackTrack.track_type == 'Master'
@sound = new Howl({
src: @urls,
autoplay: false,
loop: false,
volume: 1.0,
preload: true,
onload: @onHowlerLoad
onend: @onHowlerEnd})
onDestroyed: () =>
@sound.unload()
@ -108,12 +121,16 @@ context.JK.JamTrackPreview = class JamTrackPreview
@removeNowPlaying()
onHowlerLoad: () =>
@loading.addClass('hidden')
@loaded = true
@loading.fadeOut();
@loadingText.fadeOut(); #addClass('hidden')
play: (e) =>
if e?
e.stopPropagation()
$(this).triggerHandler(@EVENTS.PREVIEW_PLAYED)
if @no_audio
context.JK.prodBubble(@playButton, 'There is no preview available for this track.', {}, {duration:2000})
else
@ -129,7 +146,9 @@ context.JK.JamTrackPreview = class JamTrackPreview
onload: @onHowlerLoad
onend: @onHowlerEnd})
unless @loaded
@loading.removeClass('hidden')
@loadingText.removeClass('hidden')
@logger.debug("play issued for jam track preview")

View File

@ -6,7 +6,8 @@ context.JK.JamTrackScreen=class JamTrackScreen
LIMIT = 10
instrument_logo_map = context.JK.getInstrumentIconMap24()
constructor: (@app) ->
constructor: (@app) ->
@EVENTS = context.JK.EVENTS
@logger = context.JK.logger
@screen = null
@content = null
@ -21,6 +22,7 @@ context.JK.JamTrackScreen=class JamTrackScreen
@next = null
@currentQuery = this.defaultQuery()
@expanded = null
@shownHelperBubbles = false
beforeShow:(data) =>
this.setFilterFromURL()
@ -38,11 +40,17 @@ context.JK.JamTrackScreen=class JamTrackScreen
)
else
this.refresh()
unless @shownHelperBubbles
@shownHelperBubbles = true
@startHelperBubbles()
afterShow:(data) =>
context.JK.Tracking.jamtrackBrowseTrack(@app)
beforeHide: () =>
this.clearCtaHelpTimeout()
this.clearBandFilterHelpTimeout()
this.clearMasterHelpTimeout()
this.clearResults();
events:() =>
@ -57,6 +65,104 @@ context.JK.JamTrackScreen=class JamTrackScreen
@noMoreJamtracks.hide()
@next = null
startHelperBubbles: () =>
@showBandFilterHelpTimeout = setTimeout(@showBandFilterHelp, 3500)
showBandFilterHelp: () =>
context.JK.HelpBubbleHelper.jamtrackBrowseBand(@artist.closest('.easydropdown-wrapper'), $('body'))
@showMasterHelpDueTime = new Date().getTime() + 11000 # 6000 ms for band tooltip to display, and 5 seconds of quiet time
@scroller.on('scroll', @masterHelpScrollWatch)
@scroller.on('scroll', @clearBubbles)
@showMasterHelpTimeout = setTimeout(@showMasterHelp, @masterHelpDueTime())
clearBubbles: () =>
if @helpBubble?
@helpBubble.btOff()
@helpBubble = null
# computes when we should show the master help bubble
masterHelpDueTime: () =>
dueTime = @showMasterHelpDueTime - new Date().getTime()
if dueTime <= 0
dueTime = 2000
dueTime
# computes when we should show the master help bubble
ctaHelpDueTime: () =>
dueTime = @showCtaHelpDueTime - new Date().getTime()
if dueTime <= 0
dueTime = 2000
dueTime
# if the user scrolls, reset the master help due time
masterHelpScrollWatch: () =>
@clearMasterHelpTimeout()
@showMasterHelpTimeout = setTimeout(@showMasterHelp, @masterHelpDueTime() + 2000)
# if the user scrolls, reset the master help due time
ctaHelpScrollWatch: () =>
@clearCtaHelpTimeout()
@showCtaHelpTimeout = setTimeout(@showCtaHelp, @ctaHelpDueTime() + 2000)
showCtaHelp: () =>
@scroller.off('scroll', @ctaHelpScrollWatch)
@clearCtaHelpTimeout()
cutoff = @scroller.offset().top;
@screen.find('.jamtrack-actions').each((i, element) =>
$element = $(element)
if ($element.offset().top >= cutoff)
@helpBubble = context.JK.HelpBubbleHelper.jamtrackBrowseCta($element, $('body'))
return false
else
return true
)
showMasterHelp: () =>
@scroller.off('scroll', @masterHelpScrollWatch)
@clearMasterHelpTimeout()
# don't show the help if the user has already clicked a preview
unless @userPreviewed
cutoff = @scroller.offset().top;
@screen.find('.jamtrack-preview[data-track-type="Master"]').each((i, element) =>
$element = $(element)
if ($element.offset().top >= cutoff)
@helpBubble = context.JK.HelpBubbleHelper.jamtrackBrowseMasterMix($element.find('.play-button'), $('body'))
return false
else
return true
)
@showCtaHelpDueTime = new Date().getTime() + 11000
@scroller.on('scroll', @ctaHelpScrollWatch)
@showCtaHelpTimeout = setTimeout(@showCtaHelp, @ctaHelpDueTime()) # 6000 ms for bubble show time, and 5000ms for delay
previewPlayed: () =>
@userPreviewed = true
clearCtaHelpTimeout:() =>
if @showCtaHelpTimeout?
clearTimeout(@showCtaHelpTimeout)
@showCtaHelpTimeout = null
clearBandFilterHelpTimeout: () =>
if @showBandFilterHelpTimeout?
clearTimeout(@showBandFilterHelpTimeout)
@showBandFilterHelpTimeout = null
clearMasterHelpTimeout: () =>
if @showMasterHelpTimeout?
clearTimeout(@showMasterHelpTimeout)
@showMasterHelpTimeout = null
setFilterFromURL:() =>
# Grab parms from URL for artist, instrument, and availability
parms=this.getParams()
@ -73,7 +179,9 @@ context.JK.JamTrackScreen=class JamTrackScreen
@availability.val(parms.availability)
else
@availability.val('')
window.history.replaceState({}, "", "/client#/jamtrackBrowse")
if window.history.replaceState #ie9 proofing
window.history.replaceState({}, "", "/client#/jamtrackBrowse")
getParams:() =>
params = {}
@ -320,7 +428,8 @@ context.JK.JamTrackScreen=class JamTrackScreen
for track in jamTrack.tracks
trackRow = jamtrackElement.find("[jamtrack-track-id='#{track.id}']")
previewElement = trackRow.find(".jamtrack-preview")
new JK.JamTrackPreview(@app, previewElement, jamTrack, track, {master_shows_duration: true, color:'gray'})
preview = new JK.JamTrackPreview(@app, previewElement, jamTrack, track, {master_shows_duration: true, color:'gray'})
$(preview).on(@EVENTS.PREVIEW_PLAYED, @previewPlayed)
this.handleExpanded(jamtrackElement, false)

View File

@ -72,7 +72,8 @@ context.JK.JamTrackLanding = class JamTrackLanding
that=this
@bandList.on "click", "a.artist-link", (event)->
context.location="client#/jamtrackBrowse"
window.history.replaceState({}, "", this.href)
if window.history.replaceState # ie9 proofing
window.history.replaceState({}, "", this.href)
event.preventDefault()
handleFailure:(error) =>

View File

@ -1,3 +1,4 @@
//= require bugsnag
//= require bind-polyfill
//= require jquery
//= require jquery.monkeypatch

View File

@ -156,6 +156,9 @@
submit_data.city = $('#jam_ruby_user_city').val()
submit_data.birth_date = gather_birth_date()
submit_data.instruments = gather_instruments()
if($.QueryString['affiliate_partner_id']) {
submit_data.affiliate_partner_id = $.QueryString['affiliate_partner_id'];
}
//submit_data.photo_url = $('#jam_ruby_user_instruments').val()

View File

@ -4,12 +4,13 @@ context.JK ||= {};
context.JK.SiteValidator = class SiteValidator
constructor: (site_type, success_callback, fail_callback) ->
constructor: (site_type, success_callback, fail_callback, parent) ->
@EVENTS = context.JK.EVENTS
@rest = context.JK.Rest()
@site_type = site_type
@input_div = $(".site_validator#"+site_type+"_validator")
@data_input = @input_div.find('input')
@input_div = $(".site_validator."+site_type+"_validator", parent)
@data_input = @input_div.find('input')
@logger = context.JK.logger
@spinner = @input_div.find('span.spinner-small')
@checkmark = @input_div.find('.validate-checkmark')
@ -33,7 +34,7 @@ context.JK.SiteValidator = class SiteValidator
@site_status = null
dataToValidate: () =>
url = @data_input.val()
url = @data_input.val()
if url && 0 < url.length
url.substring(0,2000)
else
@ -129,8 +130,8 @@ context.JK.SiteValidator = class SiteValidator
context.JK.RecordingSourceValidator = class RecordingSourceValidator extends SiteValidator
constructor: (site_type, success_callback, fail_callback) ->
super(site_type)
constructor: (site_type, success_callback, fail_callback, parent) ->
super(site_type, success_callback, fail_callback, parent)
@recording_sources = []
@is_rec_src = true
@add_btn = @input_div.find('a.add-recording-source')

View File

@ -630,6 +630,10 @@
return (suppressDay ? '' : (days[date.getDay()] + ' ')) + months[date.getMonth()] + ' ' + context.JK.padString(date.getDate(), 2) + ', ' + date.getFullYear();
}
// returns June for months 0-11
context.JK.getMonth = function(monthNumber) {
return months[monthNumber];
}
context.JK.formatDateYYYYMMDD = function(dateString) {
var date = new Date(dateString);

View File

@ -0,0 +1,55 @@
$ = jQuery
context = window
context.JK ||= {};
class AffiliateLinks
constructor: (@app, @partner_id) ->
@logger = context.JK.logger
@rest = new context.JK.Rest();
initialize: () =>
@page = $('body')
@sections = ['jamtrack_songs', 'jamtrack_bands', 'jamtrack_general', 'jamkazam', 'sessions', 'recordings']
@jamtrack_songs = @page.find('table.jamtrack_songs tbody')
@jamtrack_bands = @page.find('table.jamtrack_bands tbody')
@jamtrack_general = @page.find('table.jamtrack_general tbody')
@jamkazam = @page.find('table.jamkazam tbody')
@sessions= @page.find('table.sessions tbody')
@recordings = @page.find('table.recordings tbody')
@iterate()
onGetLinks: (links) =>
$table = @page.find('table.' + @section + ' tbody')
template = $('#template-affiliate-link-row').html();
context._.each(links, (item) =>
$link = $(context._.template(template, item, {variable: 'data'}));
$link.find('td.copy-link a').click(@copyLink)
$table.append($link)
)
if @sections.length > 0
@iterate()
copyLink: () ->
$element = $(this)
$url = $element.closest('tr').find('td.url input')
$url.select()
return false;
iterate: () =>
@section = @sections.shift()
@rest.getLinks(@section, @partner_id)
.done(@onGetLinks)
context.JK.AffiliateLinks = AffiliateLinks

View File

@ -0,0 +1,119 @@
$ = jQuery
context = window
context.JK ||= {};
class AffiliateProgram
constructor: (@app) ->
@logger = context.JK.logger
@rest = new context.JK.Rest();
@agreeBtn = null
@disagreeBtn = null
@entityForm = null
@disagreeNotice = null
@entityName = null
@entityType = null
@entityRadio = null
@fieldEntityName = null
@fieldEntityType = null
@entityOptions = null
removeErrors: () =>
@fieldEntityName.removeClass('error').find('.error-info').remove();
@fieldEntityType.removeClass('error').find('.error-info').remove();
@entityOptions.removeClass('error').find('.error-info').remove();
onRadioChanged: () =>
@removeErrors()
value = @page.find('input:radio[name="entity"]:checked').val()
if value == 'individual'
@entityForm.slideUp()
else
@entityForm.slideDown()
return false
onCreatedAffiliatePartner:(response) =>
if response.partner_user_id?
# this was an existing user, so tell them to go on in
context.JK.Banner.show({buttons: [{name: 'GO TO AFFILIATE PAGE', href: '/client#/account/affiliatePartner'}], title: 'congratulations', html: 'Thank you for joining the JamKazam affiliate program!<br/><br/>You can visit the <a href="/client#/account/affiliatePartner">Affiliate Page</a> in your JamKazam Account any time to get links to share to refer users, and to view reports on affiliate activity levels.'})
else
context.JK.Banner.show({buttons: [{name: 'GO SIGNUP', href:'/signup?affiliate_partner_id=' + response.id}], title: 'congratulations', html: 'Thank you for joining the JamKazam affiliate program!<br/><br/>There is still one more step: you still need to create a user account on JamKazam, so that you can access your affiliate information.'})
onFailedCreateAffiliatePartner: (jqXHR) =>
if jqXHR.status == 422
body = JSON.parse(jqXHR.responseText)
if body.errors && body.errors.affiliate_partner && body.errors.affiliate_partner[0] == 'You are already an affiliate.'
@app.notify({title:'Error', text:'You are already an affiliate.'})
else
@app.notifyServerError(jqXHR, 'Unable to Create Affiliate')
else
@app.notifyServerError(jqXHR, 'Unable to Create Affiliate')
onAgreeClicked: () =>
@removeErrors()
value = @page.find('input:radio[name="entity"]:checked').val()
error = false
if value?
if value == 'individual'
entityType = 'Individual'
else
# insist that they fill out entity type info
entityName = @entityName.val()
entityType = @entityType.val()
entityNameNotEmpty = !!entityName
entityTypeNotEmpty = !!entityType
if !entityNameNotEmpty
@fieldEntityName.addClass('error').append('<div class="error-info">must be specified</div>')
error = true
if !entityTypeNotEmpty
@fieldEntityType.addClass('error').append('<div class="error-info">must be specified</div>')
error = true
else
@entityOptions.addClass('error')
error = true
unless error
@rest.createAffiliatePartner({partner_name: entityName, entity_type: entityType})
.done(@onCreatedAffiliatePartner)
.fail(@onFailedCreateAffiliatePartner)
@disagreeNotice.hide ('hidden')
return false
onDisagreeClicked: () =>
@removeErrors()
@disagreeNotice.slideDown('hidden')
return false
events:() =>
@entityRadio.on('change', @onRadioChanged)
@agreeBtn.on('click', @onAgreeClicked)
@disagreeBtn.on('click', @onDisagreeClicked)
initialize: () =>
@page = $('body')
@agreeBtn = @page.find('.agree-button')
@disagreeBtn = @page.find('.disagree-button')
@entityForm = @page.find('.entity-info')
@disagreeNotice = @page.find('.disagree-text')
@entityName = @page.find('input[name="entity-name"]')
@entityType = @page.find('select[name="entity-type"]')
@entityRadio = @page.find('input[name="entity"]')
@fieldEntityName = @page.find('.field.entity.name')
@fieldEntityType = @page.find('.field.entity.type')
@entityOptions = @page.find('.entity-options')
@events()
context.JK.AffiliateProgram = AffiliateProgram

View File

@ -44,12 +44,14 @@
$previews.append($element);
new context.JK.JamTrackPreview(app, $element, jam_track, track, {master_shows_duration: false, color:'black', master_adds_line_break: true})
new context.JK.JamTrackPreview(app, $element, jam_track, track, {master_shows_duration: false, color:'black', master_adds_line_break: true, preload_master:true})
if(track.track_type =='Master') {
context.JK.HelpBubbleHelper.rotateJamTrackLandingBubbles($element.find('.jam-track-preview'), $page.find('.video-wrapper'), $page.find('.cta-free-jamtrack a'), $page.find('a.browse-jamtracks'));
}
})
$previews.append('<br clear = "all" />')
})
.fail(function () {
app.notify({title: 'Unable to fetch JamTrack', text: "Please refresh the page or try again later."})

View File

@ -34,7 +34,11 @@
$previews.append($element);
new context.JK.JamTrackPreview(app, $element, jam_track, track, {master_shows_duration: false, color:'black', master_adds_line_break:true})
new context.JK.JamTrackPreview(app, $element, jam_track, track, {master_shows_duration: false, color:'black', master_adds_line_break:true, preload_master:true})
if(track.track_type =='Master') {
context.JK.HelpBubbleHelper.rotateJamTrackLandingBubbles($element.find('.jam-track-preview'), $page.find('.video-wrapper'), $page.find('.cta-free-jamtrack a'), $page.find('a.browse-jamtracks'));
}
})
$previews.append('<br clear = "all" />')

View File

@ -1,3 +1,4 @@
//= require bugsnag
//= require bind-polyfill
//= require jquery
//= require jquery.monkeypatch
@ -20,6 +21,7 @@
//= require jquery.icheck
//= require jquery.bt
//= require jquery.exists
//= require jquery.visible
//= require howler.core.js
//= require AAA_Log
//= require AAC_underscore
@ -65,6 +67,8 @@
//= require web/tracking
//= require web/individual_jamtrack
//= require web/individual_jamtrack_band
//= require web/affiliate_program
//= require web/affiliate_links
//= require fakeJamClient
//= require fakeJamClientMessages
//= require fakeJamClientRecordings

View File

@ -884,7 +884,11 @@
// deal with sample rate
if(selectedDeviceInfo && context.jamClient.FTUESetPreferredMixerSampleRate) {
// get the preferred sample rate for the device, and set that as the initial value of the sample rate dropdown
if (selectedDeviceInfo.input.info.port_audio_name != 'Default Input') {
// except for WKS; we just default to 48
if (selectedDeviceInfo.input.info.type == 'Win32_wdm') {
var inputSampleRate = 48000;
}
else if (selectedDeviceInfo.input.info.port_audio_name != 'Default Input') {
var inputSampleRate = context.jamClient.FTUEgetInputDeviceSampleRate(selectedDeviceInfo.input.info.port_audio_name);
}
else {

View File

@ -197,7 +197,7 @@
}
var devices = context.jamClient.FTUEGetAudioDevices();
logger.debug("FTUEGetAudioDevices: " + JSON.stringify(devices));
//logger.debug("FTUEGetAudioDevices: " + JSON.stringify(devices));
var loadedDevices = {};

View File

@ -21,6 +21,13 @@
}
}
hr {
height:0;
border-width: 1px 0 0 0;
border-style:solid;
border-color:$ColorTextTypical;
}
h4 {
margin-top:8px;
margin-bottom: 10px;
@ -57,7 +64,7 @@
.subcaption {
margin-bottom: 4px;
}
}
}
.webcam-container {

View File

@ -1,10 +1,26 @@
@import "common.css.scss";
@import "site_validator.css.scss";
.profile-online-sample-controls {
.profile-online-sample-controls {
table.profile-table {
width: 100%;
tr:nth-child(even) td {
padding: 0.25em 0.25em 1em 0.25em;
vertical-align: top;
}
tr:nth-child(odd) td {
padding: 0.25em;
vertical-align: top;
}
}
table.control-table {
width: 100%;
}
.sample-row {
position: relative;
clear: both;
}
.sample {
@ -13,54 +29,13 @@
}
.presence {
margin: 3px 30px 15px 0px;
input {
width:200px;
}
margin: 3px 30px 15px 0px;
}
.samples-container {
overflow: hidden;
.samples {
width: 30%;
margin: 0.5em;
label {
height: 1.5em;
}
.controls {
height: 2em;
}
input {
width: 200px;
}
.sample {
margin: 3px 5px 10px 0px;
a.add-recording-source {
margin-top: 2px !important;
}
}
div.sample-list {
min-height: 150px;
width: auto;
min-width: 100px;
border: 2px solid #ccc;
overflow: scroll;
}
div.entry {
margin-left: 10px;
margin-top: 5px;
}
div.close-button {
margin-right: 15px;
margin-top: 5px;
cursor: pointer;
}
.site_validator {
a, .spinner-small {
margin: 1px 1px 2px 2px;
vertical-align: top;
}
}
}

View File

@ -0,0 +1,201 @@
@import 'common.css.scss';
#account-affiliate-partner {
p {
font-size: 15px;
line-height: 125%;
margin:0;
}
.affiliates-header {
float:left;
}
.affiliate-partner-nav {
width:85%;
position:relative;
float:right;
margin-bottom:20px;
}
.affiliate-partner-nav a {
width:19%;
text-align:center;
height: 27px;
display: block;
float:right;
margin-right:5px;
vertical-align:bottom;
padding-top:10px;
background-color:#535353;
color:#ccc;
font-size:17px;
text-decoration:none;
}
.affiliate-partner-nav a:hover {
background-color:#666;
color:#fff;
}
.affiliate-partner-nav a.active {
background-color:#ed3618;
color:#fff;
}
.affiliate-partner-nav a.active:hover {
background-color:#ed3618;
cursor:default;
}
.affiliate-partner-nav a.last {
margin-right:0px !important;
}
#affiliate-partner-tab-content {
.tab-account {
.left-col {
float: left;
width: 55%;
.affiliate-label {
text-align: left;
width: 130px;
float: left;
display: inline-block;
margin-right: 10px;
margin-top: 3px;
}
input {
margin-bottom: 5px;
@include border_box_sizing;
}
.button-orange {
margin-right:2px;
}
.spacer {
width:140px;
display:inline-block;
}
.input-buttons {
width:60%;
display:inline-block;
}
}
.right-col {
float: right;
width: 45%;
margin-top: 30px;
line-height:125%;
}
}
}
.links {
p.prompt {
margin-bottom:20px;
}
}
.link-type-section {
border-width:0 0 1px 0;
border-style:solid;
border-color:$ColorTextTypical;
padding-bottom:20px;
margin-bottom:20px;
label {
display:inline-block;
margin-right:20px;
}
select {
}
}
.link-type-prompt {
display:none;
p {
margin-bottom:20px;
}
.example-link {
font-weight:bold;
}
}
table.traffic-table {
width:400px;
.signups, .visits {
width:70px;
text-align:right;
}
}
table.links-table {
min-width:100%;
margin-top:20px;
th {
padding: 10px 0 20px;
white-space:nowrap;
}
td {
padding:3px 0;
white-space:nowrap;
}
.target {
width:45%;
white-space:normal;
}
.copy-link {
width:100px;
}
.url {
input {
background-color: transparent;
-webkit-box-shadow:none;
box-shadow:none;
color:#ccc;
}
}
}
.agreement {
#partner-agreement-v1 {
height:340px;
overflow:scroll;
padding:20px;
border:1px solid white;
@include border-box_sizing;
}
label.partner-agreement {
display:inline-block;
margin-right:40px;
}
h2 {
font-size:20px;
font-weight:bold;
margin:40px 30px 20px 0;
display:inline-block;
}
.execution-date {
display:inline-block;
}
.input-buttons {
margin-top:20px;
}
}
}

View File

@ -9,6 +9,7 @@
* compiled file, but it's generally better to create a new file per style scope.
*
*= require_self
*= require web/Raleway
*= require jquery.ui.datepicker
*= require ./ie
*= require jquery.bt
@ -35,6 +36,7 @@
*= require ./accountProfileInterests
*= require ./accountProfileSamples
*= require ./accountPaymentHistory
*= require ./account_affiliate
*= require ./search
*= require ./ftue
*= require ./jamServer
@ -81,4 +83,5 @@
*= require ./downloadJamTrack
*= require ./jamTrackPreview
*= require users/signinCommon
*= require landings/partner_agreement_v1
*/

View File

@ -50,6 +50,8 @@ body.jam, body.web, .dialog{
.big-help {
font-size:20px;
color: #ed3618;
p {color:#ed3618}
}
.help-hover-recorded-tracks, .help-hover-stream-mix, .help-hover-recorded-backing-tracks {

View File

@ -69,6 +69,19 @@
}
}
.loading-text {
position:absolute;
right:-135px;
top:0;
padding:0 3px;
font-style:italic;
z-index:1;
-webkit-border-radius:10px;
-moz-border-radius:10px;
border-radius:10px;
border:0 solid black;
}
.adds-line-break {
display:block;
margin-left:66px;

View File

@ -24,11 +24,11 @@ body {
height: 100%;
overflow: hidden;
font-size: 14px;
font-family: Raleway, Arial, Helvetica, sans-serif;
font-family: 'Raleway', Arial, Helvetica, sans-serif;
font-weight: 300;
input,textarea {
font-family: Raleway, Arial, Helvetica, sans-serif;
font-family: 'Raleway', Arial, Helvetica, sans-serif;
font-weight: 300;
}
}
@ -556,22 +556,22 @@ hr {
}
::-webkit-input-placeholder {
font-family: Raleway, Arial, Helvetica, sans-serif;
font-family: 'Raleway', Arial, Helvetica, sans-serif;
font-size:14px;
}
:-moz-placeholder { /* Firefox 18- */
font-family: Raleway, Arial, Helvetica, sans-serif;
font-family: 'Raleway', Arial, Helvetica, sans-serif;
font-size:14px;
}
::-moz-placeholder { /* Firefox 19+ */
font-family: Raleway, Arial, Helvetica, sans-serif;
font-family: 'Raleway', Arial, Helvetica, sans-serif;
font-size:14px;
}
:-ms-input-placeholder {
font-family: Raleway, Arial, Helvetica, sans-serif;
font-family: 'Raleway', Arial, Helvetica, sans-serif;
font-size:14px;
}

View File

@ -198,20 +198,21 @@
.jam-track-preview {
font-size:11px;
white-space:nowrap;
.loading-text {
right:-115px;
background-color:#262626;
}
}
.jamtrack-action {
@include border_box_sizing;
width: 20%;
text-align: center;
vertical-align: middle;
.jamtrack-action-container {
display: flex;
align-items: center;
justify-content: center;
position:absolute;
height:100%;
width:100%;
display:inline-block;
}
.play-button {

View File

@ -7,7 +7,7 @@
overflow:auto;
border:1px solid #ED3618;
text-align:left;
font-family: Raleway, Arial, Helvetica, sans-serif;
font-family: 'Raleway', Arial, Helvetica, sans-serif;
ul {
height:100%;
margin-left:20px;

View File

@ -8,7 +8,7 @@
overflow:auto;
border:1px solid #ED3618;
text-align:center;
font-family: Raleway, Arial, Helvetica, sans-serif;
font-family: 'Raleway', Arial, Helvetica, sans-serif;
ul {
@include vertical-align-column;
height:100%;

Some files were not shown because too many files have changed in this diff Show More