Compare commits
46 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
64a93dd42f | |
|
|
eb52813822 | |
|
|
8613a03d00 | |
|
|
8f0b8929ba | |
|
|
08e1c5274d | |
|
|
5f347ccfac | |
|
|
ca2bf19b7a | |
|
|
fad1f9d6d9 | |
|
|
51838fb413 | |
|
|
45cb401112 | |
|
|
9078515984 | |
|
|
52ae83e2f4 | |
|
|
b454cf9ead | |
|
|
65f5624ff9 | |
|
|
e82450dfe9 | |
|
|
f1992eaa78 | |
|
|
bdeecc76c6 | |
|
|
8c8024c12b | |
|
|
c183af3d6b | |
|
|
ab424c21d2 | |
|
|
753f35b24d | |
|
|
624853c868 | |
|
|
71d8571eb9 | |
|
|
fae16c7483 | |
|
|
253a6c566b | |
|
|
ed561be4b5 | |
|
|
eb69640667 | |
|
|
9b0a9d1f32 | |
|
|
e1cc5483a2 | |
|
|
73d2a7a020 | |
|
|
eb298d6859 | |
|
|
93c4154648 | |
|
|
fbd871d204 | |
|
|
0d82f6ee16 | |
|
|
1279b16ec0 | |
|
|
e3cff0a825 | |
|
|
6a6e4cde09 | |
|
|
1dd15fb0aa | |
|
|
9282369e54 | |
|
|
828191d683 | |
|
|
c0031cfe3d | |
|
|
9b17546082 | |
|
|
eed3d51f4b | |
|
|
4eac68b645 | |
|
|
fe6157e8cf | |
|
|
675bf2b69c |
|
|
@ -0,0 +1,185 @@
|
||||||
|
class Spacer
|
||||||
|
def self.spacer(val, row)
|
||||||
|
|
||||||
|
percentage = ((val * 100) / row.total.to_f).round(1).to_s
|
||||||
|
('%-5.5s' % percentage).gsub(' ', ' ') + '% - ' + val.to_s
|
||||||
|
end
|
||||||
|
end
|
||||||
|
=begin
|
||||||
|
select
|
||||||
|
count(id) as total,
|
||||||
|
count(first_downloaded_client_at) as downloaded,
|
||||||
|
count(first_ran_client_at) as ran_client,
|
||||||
|
count(first_certified_gear_at) as ftue,
|
||||||
|
count(first_music_session_at) as any_session,
|
||||||
|
count(first_real_music_session_at) as real_session,
|
||||||
|
count(first_good_music_session_at) as good_session,
|
||||||
|
count(first_invited_at) as invited,
|
||||||
|
count(first_friended_at) as friended,
|
||||||
|
count(first_subscribed_at) as subscribed
|
||||||
|
from users where users.created_at >= '2024-11-01' AND users.created_at < '2025-04-01'
|
||||||
|
|
||||||
|
select first_name, last_name, email
|
||||||
|
from users where users.created_at >= '2024-11-01' AND users.created_at < '2025-04-01'
|
||||||
|
AND first_music_session_at is NULL;
|
||||||
|
|
||||||
|
=end
|
||||||
|
|
||||||
|
ActiveAdmin.register_page "Jammers Subscription Cohorts" do
|
||||||
|
menu :parent => 'Reports'
|
||||||
|
|
||||||
|
content :title => "Jammers Subscription Cohorts" do
|
||||||
|
|
||||||
|
filter_type = params[:filter_type] || 'All'
|
||||||
|
filter_campaign = params[:filter_campaign]
|
||||||
|
filter_campaign_id = params[:filter_campaign_id]
|
||||||
|
filter_ad_set = params[:filter_ad_set]
|
||||||
|
filter_ad_name = params[:filter_ad_name]
|
||||||
|
|
||||||
|
campaigns = User.where("origin_utm_medium = 'cpc'").distinct.pluck(:origin_utm_campaign).compact.sort
|
||||||
|
campaign_ids = User.where("origin_utm_medium = 'cpc'").distinct.pluck(:origin_id).compact.sort
|
||||||
|
ad_sets = User.where("origin_utm_medium = 'cpc'").distinct.pluck(:origin_term).compact.sort
|
||||||
|
ad_names = User.where("origin_utm_medium = 'cpc'").distinct.pluck(:origin_content).compact.sort
|
||||||
|
|
||||||
|
div style: "margin-bottom: 20px; padding: 10px; background-color: #f4f4f4; border-radius: 4px;" do
|
||||||
|
form action: admin_jammers_subscription_cohorts_path, method: :get do
|
||||||
|
span "Source: ", style: "font-weight: bold; margin-right: 5px;"
|
||||||
|
select name: 'filter_type', onchange: 'this.form.submit()', style: "margin-right: 15px;" do
|
||||||
|
option "All", value: 'All', selected: filter_type == 'All'
|
||||||
|
option "Organic", value: 'Organic', selected: filter_type == 'Organic'
|
||||||
|
option "Advertising", value: 'Advertising', selected: filter_type == 'Advertising'
|
||||||
|
end
|
||||||
|
|
||||||
|
if filter_type == 'Advertising'
|
||||||
|
div style: "margin-top: 10px;" do
|
||||||
|
span "Campaign Name: ", style: "font-weight: bold; margin-right: 5px;"
|
||||||
|
select name: 'filter_campaign', onchange: 'this.form.submit()', style: "margin-right: 15px;" do
|
||||||
|
option "All", value: ''
|
||||||
|
option "Null", value: 'NULL', selected: filter_campaign == 'NULL'
|
||||||
|
campaigns.each do |c|
|
||||||
|
option c, value: c, selected: filter_campaign == c
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
div style: "margin-top: 10px;" do
|
||||||
|
span "Campaign ID: ", style: "font-weight: bold; margin-right: 5px;"
|
||||||
|
select name: 'filter_campaign_id', onchange: 'this.form.submit()', style: "margin-right: 15px;" do
|
||||||
|
option "All", value: ''
|
||||||
|
option "Null", value: 'NULL', selected: filter_campaign_id == 'NULL'
|
||||||
|
campaign_ids.each do |c|
|
||||||
|
option c, value: c, selected: filter_campaign_id == c
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
div style: "margin-top: 10px;" do
|
||||||
|
span "Ad Set: ", style: "font-weight: bold; margin-right: 5px;"
|
||||||
|
select name: 'filter_ad_set', onchange: 'this.form.submit()', style: "margin-right: 15px;" do
|
||||||
|
option "All", value: ''
|
||||||
|
option "Null", value: 'NULL', selected: filter_ad_set == 'NULL'
|
||||||
|
ad_sets.each do |c|
|
||||||
|
option c, value: c, selected: filter_ad_set == c
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
div style: "margin-top: 10px;" do
|
||||||
|
span "Ad Name: ", style: "font-weight: bold; margin-right: 5px;"
|
||||||
|
select name: 'filter_ad_name', onchange: 'this.form.submit()', style: "margin-right: 15px;" do
|
||||||
|
option "All", value: ''
|
||||||
|
option "Null", value: 'NULL', selected: filter_ad_name == 'NULL'
|
||||||
|
ad_names.each do |c|
|
||||||
|
option c, value: c, selected: filter_ad_name == c
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
noscript { input type: :submit, value: "Filter" }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
h2 "Users Grouped By Month as Paying Subscribers"
|
||||||
|
|
||||||
|
query = User.select(%Q{date_trunc('month', users.created_at) as month,
|
||||||
|
count(id) as total,
|
||||||
|
count(first_downloaded_client_at) as downloaded,
|
||||||
|
count(first_ran_client_at) as ran_client,
|
||||||
|
count(first_certified_gear_at) as ftue,
|
||||||
|
count(first_music_session_at) as any_session,
|
||||||
|
count(first_real_music_session_at) as real_session,
|
||||||
|
count(first_good_music_session_at) as good_session,
|
||||||
|
count(first_invited_at) as invited,
|
||||||
|
count(first_friended_at) as friended,
|
||||||
|
count(first_subscribed_at) as subscribed,
|
||||||
|
count(first_played_jamtrack_at) as played_jamtrack
|
||||||
|
})
|
||||||
|
.joins(%Q{LEFT JOIN LATERAL (
|
||||||
|
SELECT
|
||||||
|
j.created_at
|
||||||
|
FROM
|
||||||
|
jam_track_rights as j
|
||||||
|
WHERE
|
||||||
|
j.user_id = users.id
|
||||||
|
ORDER BY
|
||||||
|
j.created_at
|
||||||
|
LIMIT 1 -- Select only that single row
|
||||||
|
) j ON TRUE })
|
||||||
|
|
||||||
|
if filter_type == 'Organic'
|
||||||
|
query = query.where("users.origin_utm_source = 'organic'")
|
||||||
|
elsif filter_type == 'Advertising'
|
||||||
|
query = query.where("origin_utm_medium = 'cpc'")
|
||||||
|
|
||||||
|
if filter_campaign.present?
|
||||||
|
if filter_campaign == 'NULL'
|
||||||
|
query = query.where("users.origin_utm_campaign IS NULL")
|
||||||
|
elsif filter_campaign != 'All'
|
||||||
|
query = query.where("users.origin_utm_campaign = ?", filter_campaign)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if filter_campaign_id.present?
|
||||||
|
if filter_campaign_id == 'NULL'
|
||||||
|
query = query.where("users.origin_id IS NULL")
|
||||||
|
elsif filter_campaign_id != 'All'
|
||||||
|
query = query.where("users.origin_id = ?", filter_campaign_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if filter_ad_set.present?
|
||||||
|
if filter_ad_set == 'NULL'
|
||||||
|
query = query.where("users.origin_term IS NULL")
|
||||||
|
elsif filter_ad_set != 'All'
|
||||||
|
query = query.where("users.origin_term = ?", filter_ad_set)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if filter_ad_name.present?
|
||||||
|
if filter_ad_name == 'NULL'
|
||||||
|
query = query.where("users.origin_content IS NULL")
|
||||||
|
elsif filter_ad_name != 'All'
|
||||||
|
query = query.where("users.origin_content = ?", filter_ad_name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
table_for query.group("date_trunc('month', users.created_at)")
|
||||||
|
.where("j.created_at IS NULL OR (j.created_at - users.created_at) >= INTERVAL '2 hours'")
|
||||||
|
.order("date_trunc('month', users.created_at) DESC") do |row|
|
||||||
|
column "Month", Proc.new { |user| user.month.strftime('%B %Y') }
|
||||||
|
column "Total", :total
|
||||||
|
column "Subscribed", Proc.new { |user| raw(Spacer.spacer(user.subscribed, user)) }
|
||||||
|
column "Downloaded", Proc.new { |user| raw(Spacer.spacer(user.downloaded, user)) }
|
||||||
|
column "Ran Client", Proc.new { |user| raw(Spacer.spacer(user.ran_client, user)) }
|
||||||
|
column "FTUE", Proc.new { |user| raw(Spacer.spacer(user.ftue, user)) }
|
||||||
|
column "Any Session", Proc.new { |user| raw(Spacer.spacer(user.any_session, user)) }
|
||||||
|
column "2+ Session", Proc.new { |user| raw(Spacer.spacer(user.real_session, user)) }
|
||||||
|
column "Good Session", Proc.new { |user| raw(Spacer.spacer(user.good_session, user)) }
|
||||||
|
column "Invited", Proc.new { |user| raw(Spacer.spacer(user.invited, user)) }
|
||||||
|
column "Friended", Proc.new { |user| raw(Spacer.spacer(user.friended, user)) }
|
||||||
|
column "Played JT", Proc.new { |user| raw(Spacer.spacer(user.played_jamtrack, user)) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
@ -25,13 +25,12 @@ from users where users.created_at >= '2024-11-01' AND users.created_at < '2025-0
|
||||||
|
|
||||||
=end
|
=end
|
||||||
|
|
||||||
ActiveAdmin.register_page "Subscription Cohorts" do
|
ActiveAdmin.register_page "JamTrack Subscription Cohorts" do
|
||||||
menu :parent => 'Reports'
|
menu :parent => 'Reports'
|
||||||
|
|
||||||
|
content :title => "JamTrack Subscription Cohorts" do
|
||||||
content :title => "Subscription Cohorts" do
|
|
||||||
h2 "Users Grouped By Month as Paying Subscribers"
|
h2 "Users Grouped By Month as Paying Subscribers"
|
||||||
table_for User.select(%Q{date_trunc('month', created_at) as month,
|
table_for User.select(%Q{date_trunc('month', users.created_at) as month,
|
||||||
count(id) as total,
|
count(id) as total,
|
||||||
count(first_downloaded_client_at) as downloaded,
|
count(first_downloaded_client_at) as downloaded,
|
||||||
count(first_ran_client_at) as ran_client,
|
count(first_ran_client_at) as ran_client,
|
||||||
|
|
@ -41,8 +40,21 @@ ActiveAdmin.register_page "Subscription Cohorts" do
|
||||||
count(first_good_music_session_at) as good_session,
|
count(first_good_music_session_at) as good_session,
|
||||||
count(first_invited_at) as invited,
|
count(first_invited_at) as invited,
|
||||||
count(first_friended_at) as friended,
|
count(first_friended_at) as friended,
|
||||||
count(first_subscribed_at) as subscribed
|
count(first_subscribed_at) as subscribed,
|
||||||
}).group("date_trunc('month', created_at)").order("date_trunc('month', created_at) DESC") do |row|
|
count(first_played_jamtrack_at) as played_jamtrack
|
||||||
|
})
|
||||||
|
.joins(%Q{INNER JOIN LATERAL (
|
||||||
|
SELECT
|
||||||
|
j.created_at
|
||||||
|
FROM
|
||||||
|
jam_track_rights as j
|
||||||
|
WHERE
|
||||||
|
j.user_id = users.id
|
||||||
|
ORDER BY
|
||||||
|
j.created_at
|
||||||
|
LIMIT 1 -- Select only that single row
|
||||||
|
) j ON (j.created_at - users.created_at) < INTERVAL '2 hours' })
|
||||||
|
.group("date_trunc('month', users.created_at)").order("date_trunc('month', users.created_at) DESC") do |row|
|
||||||
column "Month", Proc.new { |user| user.month.strftime('%B %Y') }
|
column "Month", Proc.new { |user| user.month.strftime('%B %Y') }
|
||||||
column "Total", :total
|
column "Total", :total
|
||||||
column "Subscribed", Proc.new { |user| raw(Spacer.spacer(user.subscribed, user)) }
|
column "Subscribed", Proc.new { |user| raw(Spacer.spacer(user.subscribed, user)) }
|
||||||
|
|
@ -54,6 +66,7 @@ ActiveAdmin.register_page "Subscription Cohorts" do
|
||||||
column "Good Session", Proc.new { |user| raw(Spacer.spacer(user.good_session, user)) }
|
column "Good Session", Proc.new { |user| raw(Spacer.spacer(user.good_session, user)) }
|
||||||
column "Invited", Proc.new { |user| raw(Spacer.spacer(user.invited, user)) }
|
column "Invited", Proc.new { |user| raw(Spacer.spacer(user.invited, user)) }
|
||||||
column "Friended", Proc.new { |user| raw(Spacer.spacer(user.friended, user)) }
|
column "Friended", Proc.new { |user| raw(Spacer.spacer(user.friended, user)) }
|
||||||
|
column "Played JT", Proc.new { |user| raw(Spacer.spacer(user.played_jamtrack, user)) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -6,15 +6,18 @@ ActiveAdmin.register JamRuby::User, :as => 'UserSource' do
|
||||||
config.batch_actions = false
|
config.batch_actions = false
|
||||||
config.clear_action_items!
|
config.clear_action_items!
|
||||||
config.filters = false
|
config.filters = false
|
||||||
|
config.per_page = 250
|
||||||
|
|
||||||
scope("Most Recent First", default: true) { |scope| scope.unscoped.order('created_at desc')}
|
scope("Paid", default: true) { |scope| scope.unscoped.where(:origin_utm_medium => 'cpc').order('created_at desc') }
|
||||||
|
scope("Inorganic Source") { |scope| scope.unscoped.where("origin_utm_source != 'organic' OR origin_utm_source IS NULL").order('created_at desc') }
|
||||||
|
scope("Include Organic") { |scope| scope.unscoped.order('created_at desc') }
|
||||||
|
|
||||||
index do
|
index do
|
||||||
column "Email" do |user|
|
column "Email" do |user|
|
||||||
user.email
|
user.email
|
||||||
end
|
end
|
||||||
column "Bought TestDrive" do |user|
|
column "Signup (CST)" do |user|
|
||||||
!user.most_recent_test_drive_purchase.nil? ? "Yes" : "No"
|
user.created_at.in_time_zone("Central Time (US & Canada)")
|
||||||
end
|
end
|
||||||
column "UTM Source" do |user|
|
column "UTM Source" do |user|
|
||||||
user.origin_utm_source
|
user.origin_utm_source
|
||||||
|
|
@ -25,8 +28,23 @@ ActiveAdmin.register JamRuby::User, :as => 'UserSource' do
|
||||||
column "UTM Campaign" do |user|
|
column "UTM Campaign" do |user|
|
||||||
user.origin_utm_campaign
|
user.origin_utm_campaign
|
||||||
end
|
end
|
||||||
|
column "UTM ID" do |user|
|
||||||
|
user.origin_id
|
||||||
|
end
|
||||||
|
column "UTM Term" do |user|
|
||||||
|
user.origin_term
|
||||||
|
end
|
||||||
|
column "UTM Content" do |user|
|
||||||
|
user.origin_content
|
||||||
|
end
|
||||||
column "Referrer" do |user|
|
column "Referrer" do |user|
|
||||||
user.origin_referrer
|
user.origin_referrer
|
||||||
end
|
end
|
||||||
|
column "FB Click ID" do |user|
|
||||||
|
user.facebook_click_id
|
||||||
|
end
|
||||||
|
column "FB Browser ID" do |user|
|
||||||
|
user.facebook_browser_id
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -2,55 +2,97 @@ class ArsesController < ApplicationController
|
||||||
|
|
||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
# create or update a client_artifact row
|
def index
|
||||||
def get_or_create
|
if params[:code] != Rails.application.config.data_dump_code
|
||||||
name = params[:name]
|
render :json => {error: "Unauthorized"}, :status => 401
|
||||||
provider = params[:provider]
|
return
|
||||||
active = params[:active]
|
|
||||||
ip = params[:ip]
|
|
||||||
username = params[:username]
|
|
||||||
password = params[:password]
|
|
||||||
topology = params[:topology]
|
|
||||||
ars_id = params[:ars_id]
|
|
||||||
puts "TOPOLOGY #{topology}"
|
|
||||||
|
|
||||||
if ars_id
|
|
||||||
ars = Ars.find_by_id_int(ars_id)
|
|
||||||
end
|
|
||||||
if ars.nil?
|
|
||||||
ars = Ars.new
|
|
||||||
ars.name = name
|
|
||||||
ars.id_int = ars_id if !ars_id.nil?
|
|
||||||
end
|
end
|
||||||
|
|
||||||
ars.provider = provider
|
@arses = JamRuby::Ars.all
|
||||||
ars.active = active
|
render :json => @arses
|
||||||
ars.ip = ip
|
end
|
||||||
ars.password = password
|
|
||||||
ars.username = username
|
|
||||||
if topology
|
|
||||||
ars.city = topology['city']
|
|
||||||
ars.country = topology['country']
|
|
||||||
ars.continent = topology['continent']
|
|
||||||
ars.latitude = topology['latitude']
|
|
||||||
ars.longitude = topology['longitude']
|
|
||||||
ars.subdivision = topology['subdivision']
|
|
||||||
end
|
|
||||||
ars.save
|
|
||||||
|
|
||||||
@ars = ars
|
def update
|
||||||
unless @ars.errors.any?
|
if params[:code] != Rails.application.config.data_dump_code
|
||||||
if ars_id.nil?
|
render :json => {error: "Unauthorized"}, :status => 401
|
||||||
ars.reload
|
return
|
||||||
ars_id = ars.id_int
|
end
|
||||||
|
|
||||||
|
begin
|
||||||
|
# Primary ID lookup
|
||||||
|
@ars = JamRuby::Ars.find_by_id(params[:id])
|
||||||
|
|
||||||
|
# Explicit secondary lookups if primary ID fails
|
||||||
|
@ars ||= JamRuby::Ars.find_by_id_int(params[:id_int]) if params[:id_int]
|
||||||
|
@ars ||= JamRuby::Ars.find_by_name(params[:name]) if params[:name]
|
||||||
|
|
||||||
|
if @ars.nil?
|
||||||
|
render :json => {error: "Not Found"}, :status => 404
|
||||||
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@ars = Ars.find_by_id_int(ars_id)
|
allowed = [:password, :username, :active, :beta, :name, :provider, :id_int, :ip, :port, :continent, :country, :city, :subdivision, :latitude, :longitude]
|
||||||
render :json => {id_int: @ars.id_int, id: @ars.id, name: @ars.name, provider: @ars.provider, active: @ars.active, ip: @ars.ip}, :status => :ok
|
|
||||||
else
|
|
||||||
response.status = :unprocessable_entity
|
|
||||||
respond_with @ars
|
|
||||||
end
|
|
||||||
|
|
||||||
|
update_hash = {}
|
||||||
|
allowed.each do |attr|
|
||||||
|
update_hash[attr] = params[attr] if params.has_key?(attr)
|
||||||
|
end
|
||||||
|
|
||||||
|
if @ars.update_attributes(update_hash, as: :admin)
|
||||||
|
render :json => @ars, :status => :ok
|
||||||
|
else
|
||||||
|
render :json => @ars.errors, :status => :unprocessable_entity
|
||||||
|
end
|
||||||
|
rescue => e
|
||||||
|
render :json => {error: e.message, backtrace: e.backtrace.first(5)}, :status => 500
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# create or update a client_artifact row
|
||||||
|
def get_or_create
|
||||||
|
begin
|
||||||
|
name = params[:name]
|
||||||
|
provider = params[:provider]
|
||||||
|
active = params[:active]
|
||||||
|
beta = params.has_key?(:beta) ? params[:beta] : true
|
||||||
|
ip = params[:ip]
|
||||||
|
username = params[:username]
|
||||||
|
password = params[:password]
|
||||||
|
topology = params[:topology]
|
||||||
|
ars_id = params[:ars_id]
|
||||||
|
|
||||||
|
# Explicit field-based lookups
|
||||||
|
ars = nil
|
||||||
|
ars = JamRuby::Ars.find_by_id_int(ars_id) if ars_id
|
||||||
|
ars ||= JamRuby::Ars.find_by_name(name) if name
|
||||||
|
|
||||||
|
if ars.nil?
|
||||||
|
ars = JamRuby::Ars.new
|
||||||
|
ars.name = name
|
||||||
|
end
|
||||||
|
|
||||||
|
ars.id_int = ars_id if !ars_id.nil?
|
||||||
|
ars.provider = provider
|
||||||
|
ars.active = active
|
||||||
|
ars.beta = params[:beta]
|
||||||
|
ars.beta = beta
|
||||||
|
ars.ip = ip
|
||||||
|
ars.password = password
|
||||||
|
ars.username = username
|
||||||
|
if topology
|
||||||
|
ars.city = topology['city']
|
||||||
|
ars.country = topology['country']
|
||||||
|
ars.continent = topology['continent']
|
||||||
|
ars.latitude = topology['latitude']
|
||||||
|
ars.longitude = topology['longitude']
|
||||||
|
ars.subdivision = topology['subdivision']
|
||||||
|
end
|
||||||
|
ars.save!
|
||||||
|
|
||||||
|
@ars = ars
|
||||||
|
render :json => {id_int: @ars.id_int, id: @ars.id, name: @ars.name, provider: @ars.provider, active: @ars.active, beta: @ars.beta, ip: @ars.ip}, :status => :ok
|
||||||
|
rescue => e
|
||||||
|
render :json => {error: e.message, backtrace: e.backtrace.first(5)}, :status => 500
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,8 @@ JamAdmin::Application.routes.draw do
|
||||||
match '/api/jam_tracks/released' => 'jam_track#dump_released', :via => :get, as: 'released_jamtracks_csv'
|
match '/api/jam_tracks/released' => 'jam_track#dump_released', :via => :get, as: 'released_jamtracks_csv'
|
||||||
|
|
||||||
match '/api/arses/register' => 'arses#get_or_create', :via => :post
|
match '/api/arses/register' => 'arses#get_or_create', :via => :post
|
||||||
|
match '/api/arses' => 'arses#index', :via => :get
|
||||||
|
match '/api/arses/:id' => 'arses#update', :via => :post
|
||||||
|
|
||||||
mount Resque::Server.new, :at => "/resque"
|
mount Resque::Server.new, :at => "/resque"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,3 +38,19 @@ npm run start
|
||||||
|
|
||||||
This will open it in a borwser window at http://beta.jamkazam.local:3000. Of course for it to work you also need Rails (web) app and websocket app (websocket-gateway) running.
|
This will open it in a borwser window at http://beta.jamkazam.local:3000. Of course for it to work you also need Rails (web) app and websocket app (websocket-gateway) running.
|
||||||
|
|
||||||
|
## Working with JamTracks
|
||||||
|
|
||||||
|
if you have the latest from develop, you can go:
|
||||||
|
```
|
||||||
|
cd cicd
|
||||||
|
npm install
|
||||||
|
./export_personal_jamtracks.sh
|
||||||
|
./generate.js
|
||||||
|
open http://beta.jamkazam.local:4000/backing-tracks/ac-dc/back-in-black.html
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also do none of the above, and go straight to:
|
||||||
|
http://beta.jamkazam.local:4000/public/backing-tracks/ac-dc/back-in-black
|
||||||
|
|
||||||
|
I tried to make it so the SPA has 'secret routes' to these pages, which is convenient for us dev & testing
|
||||||
|
but also tried to make it convenient to run the cicd approach of actually generating separate pages for each landing page (which is what those 5 steps cover)
|
||||||
|
|
|
||||||
|
|
@ -57,20 +57,26 @@ const JKRegistrationForm = ({ hasLabel, jamTrack, jamTrackArtistName }) => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const addJamTrackToCart = async () => {
|
const addJamTrackToCart = async () => {
|
||||||
try{
|
try {
|
||||||
await addToCart(jamTrack);
|
await addToCart(jamTrack);
|
||||||
}catch(error){
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('currentUser', currentUser);
|
||||||
|
console.log('jamTrack', jamTrack);
|
||||||
|
console.log('jamTrackArtistName', jamTrackArtistName);
|
||||||
if (currentUser) {
|
if (currentUser) {
|
||||||
if(jamTrack){
|
if (jamTrack) {
|
||||||
|
console.log('adding jamtrack to cart');
|
||||||
addJamTrackToCart();
|
addJamTrackToCart();
|
||||||
}else if(jamTrackArtistName){
|
} else if (jamTrackArtistName) {
|
||||||
|
console.log('redirecting to jamtracks artist landing');
|
||||||
history.push(`/jamtracks?artist=${jamTrackArtistName}`);
|
history.push(`/jamtracks?artist=${jamTrackArtistName}`);
|
||||||
}else{
|
} else {
|
||||||
history.push('/public/downloads');
|
console.log('redirecting to profile');
|
||||||
|
history.push('/profile');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [currentUser, jamTrack, jamTrackArtistName]);
|
}, [currentUser, jamTrack, jamTrackArtistName]);
|
||||||
|
|
@ -140,7 +146,7 @@ const JKRegistrationForm = ({ hasLabel, jamTrack, jamTrackArtistName }) => {
|
||||||
|
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<Button color="primary" block className="mt-3" disabled={isDisabled}>
|
<Button color="primary" block className="mt-3" disabled={isDisabled}>
|
||||||
{t('signup')}
|
{t('signup')}
|
||||||
</Button>
|
</Button>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,9 @@ const Registration = () => {
|
||||||
try {
|
try {
|
||||||
const response = await getJamTrackBySlug({ slug });
|
const response = await getJamTrackBySlug({ slug });
|
||||||
const jamTrack = await response.json();
|
const jamTrack = await response.json();
|
||||||
|
console.log('jamTrack', jamTrack);
|
||||||
setJamTrack(jamTrack);
|
setJamTrack(jamTrack);
|
||||||
|
setJamTrackArtistName(jamTrack.original_artist);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,27 @@
|
||||||
import React, { Fragment } from 'react';
|
import React, { Fragment, useEffect } from 'react';
|
||||||
import { Col, Row } from 'reactstrap';
|
import { Col, Row } from 'reactstrap';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link, useHistory, useLocation } from 'react-router-dom';
|
||||||
import LoginForm from '../LoginForm';
|
import LoginForm from '../LoginForm';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useAuth } from '../../../context/UserAuth';
|
||||||
|
|
||||||
const Login = () => {
|
const Login = () => {
|
||||||
const { t } = useTranslation('auth');
|
const { t } = useTranslation('auth');
|
||||||
|
const { isAuthenticated, isLoading } = useAuth();
|
||||||
|
const history = useHistory();
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isLoading && isAuthenticated) {
|
||||||
|
const { from } = location.state || { from: { pathname: '/profile' } };
|
||||||
|
const finalDestination = from.pathname === '/' ? { pathname: '/profile' } : from;
|
||||||
|
history.replace(finalDestination);
|
||||||
|
}
|
||||||
|
}, [isAuthenticated, isLoading, history, location]);
|
||||||
|
|
||||||
|
if (isLoading || isAuthenticated) {
|
||||||
|
return null; // Or a loading spinner
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ const awesome9 = '/img/landing/jamtracks/Top 10 Image - Number 9.webp';
|
||||||
const BodyComponent = ({
|
const BodyComponent = ({
|
||||||
id = "1",
|
id = "1",
|
||||||
plan_code = "jamtrack-acdc-backinblack",
|
plan_code = "jamtrack-acdc-backinblack",
|
||||||
slug = "ac-dc-back-in-black",
|
slug = "acdc-back-in-black",
|
||||||
artist = "AC/DC (defaulted)",
|
artist = "AC/DC (defaulted)",
|
||||||
song = "Back in Black (defaulted)",
|
song = "Back in Black (defaulted)",
|
||||||
provided_jam_track = null
|
provided_jam_track = null
|
||||||
|
|
|
||||||
|
|
@ -23,12 +23,24 @@ const ProfileDropdown = () => {
|
||||||
|
|
||||||
const handleLogout = async event => {
|
const handleLogout = async event => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
const cookieDomain = `.${process.env.REACT_APP_ORIGIN}`;
|
||||||
|
console.log('handleLogout: cookie: ', cookieDomain);
|
||||||
removeCookie('remember_token', {
|
removeCookie('remember_token', {
|
||||||
domain: `.${process.env.REACT_APP_ORIGIN}`
|
domain: cookieDomain,
|
||||||
|
path: '/'
|
||||||
});
|
});
|
||||||
setCurrentUser(null);
|
setCurrentUser(null);
|
||||||
|
// This will cause the server to remove the cookie TOO (overkill),
|
||||||
|
// but this code has a side effect because something, somewhere,
|
||||||
|
// client-side history pushes to /auth/login because of this function.
|
||||||
|
// ...
|
||||||
await logout();
|
await logout();
|
||||||
|
// But I don't want to count on that side effect (defensive against future changes), so I force a page location hit here
|
||||||
window.location.href = "/auth/login";
|
window.location.href = "/auth/login";
|
||||||
|
// And here's the final bit; force a full page refresh ANYWAY.
|
||||||
|
// This is the only way we seem to really clear
|
||||||
|
// 'login state' cleanly at this point
|
||||||
|
window.location.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
/**
|
||||||
|
* meta_tracking.js
|
||||||
|
* A standalone module to capture and persist Meta attribution signals (fbclid, _fbp) in cookies.
|
||||||
|
*
|
||||||
|
* Logic adapted from web/app/assets/javascripts/meta_tracking.js for React environment.
|
||||||
|
* - Checks URL for `fbclid` and sets `_fbc` cookie.
|
||||||
|
* - Checks for `_fbp` cookie; if missing, generates and sets it.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const MetaTracking = {
|
||||||
|
init: function () {
|
||||||
|
const location = window.location;
|
||||||
|
this.handleFbc(location.search);
|
||||||
|
this.handleFbp();
|
||||||
|
this.handleUtm(location.search);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 1. Parsing and storing _fbc (Click ID)
|
||||||
|
handleFbc: function (searchParams) {
|
||||||
|
const fbclid = this.getQueryParam('fbclid', searchParams);
|
||||||
|
|
||||||
|
if (fbclid) {
|
||||||
|
const version = 'fb';
|
||||||
|
const subdomainIndex = 1; // 1 = example.com
|
||||||
|
const creationTime = new Date().getTime(); // Unix timestamp in ms
|
||||||
|
|
||||||
|
// Format: fb.1.timestamp.id
|
||||||
|
const fbcValue = `${version}.${subdomainIndex}.${creationTime}.${fbclid}`;
|
||||||
|
|
||||||
|
this.setCookie('_fbc', fbcValue, 90);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
handleUtm: function (searchParams) {
|
||||||
|
if (!searchParams) return;
|
||||||
|
|
||||||
|
const query = searchParams.substring(1);
|
||||||
|
const vars = query.split('&');
|
||||||
|
vars.forEach(v => {
|
||||||
|
const pair = v.split('=');
|
||||||
|
if (pair.length === 2) {
|
||||||
|
const key = decodeURIComponent(pair[0]);
|
||||||
|
const value = decodeURIComponent(pair[1]);
|
||||||
|
if (key.indexOf('utm_') === 0) {
|
||||||
|
this.setCookie(key, value, 90);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 2. Handling _fbp (Browser ID)
|
||||||
|
handleFbp: function () {
|
||||||
|
if (!this.getCookie('_fbp')) {
|
||||||
|
const version = 'fb';
|
||||||
|
const subdomainIndex = 1;
|
||||||
|
const creationTime = new Date().getTime();
|
||||||
|
const randomInt = Math.floor(Math.random() * 10000000000); // 10-digit random number
|
||||||
|
|
||||||
|
// Format: fb.1.timestamp.randomDigits
|
||||||
|
const fbpValue = `${version}.${subdomainIndex}.${creationTime}.${randomInt}`;
|
||||||
|
|
||||||
|
this.setCookie('_fbp', fbpValue, 90);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Helper: Get query param by name
|
||||||
|
getQueryParam: function (name, search) {
|
||||||
|
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
|
||||||
|
const regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
|
||||||
|
const results = regex.exec(search);
|
||||||
|
return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
|
||||||
|
},
|
||||||
|
|
||||||
|
// Helper: Set cookie
|
||||||
|
setCookie: function (name, value, days) {
|
||||||
|
let expires = "";
|
||||||
|
if (days) {
|
||||||
|
const date = new Date();
|
||||||
|
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
||||||
|
expires = "; expires=" + date.toUTCString();
|
||||||
|
}
|
||||||
|
// Ensure path is root and domain is included if needed (defaults to current host)
|
||||||
|
document.cookie = name + "=" + (value || "") + expires + "; path=/";
|
||||||
|
},
|
||||||
|
|
||||||
|
// Helper: Get cookie
|
||||||
|
getCookie: function (name) {
|
||||||
|
const nameEQ = name + "=";
|
||||||
|
const ca = document.cookie.split(';');
|
||||||
|
for (let i = 0; i < ca.length; i++) {
|
||||||
|
let c = ca[i];
|
||||||
|
while (c.charAt(0) === ' ') c = c.substring(1, c.length);
|
||||||
|
if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MetaTracking;
|
||||||
|
|
@ -500,9 +500,7 @@ export const getJamTrackPublic = options => {
|
||||||
export const getJamTrackBySlug = options => {
|
export const getJamTrackBySlug = options => {
|
||||||
const { slug } = options;
|
const { slug } = options;
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
apiFetch(`/jamtracks/${slug}`, {
|
apiFetch(`/jamtracks/public/${slug}`)
|
||||||
baseUrl: process.env.REACT_APP_CLIENT_BASE_URL
|
|
||||||
})
|
|
||||||
.then(response => resolve(response))
|
.then(response => resolve(response))
|
||||||
.catch(error => reject(error));
|
.catch(error => reject(error));
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, {useEffect} from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { Route, Switch } from 'react-router-dom';
|
import { Route, Switch } from 'react-router-dom';
|
||||||
import { toast, ToastContainer } from 'react-toastify';
|
import { toast, ToastContainer } from 'react-toastify';
|
||||||
import { CloseButton, Fade } from '../components/common/Toast';
|
import { CloseButton, Fade } from '../components/common/Toast';
|
||||||
|
|
@ -8,6 +8,7 @@ import ErrorLayout from './ErrorLayout';
|
||||||
import BuildMeta from "./JKBuildMeta";
|
import BuildMeta from "./JKBuildMeta";
|
||||||
|
|
||||||
import loadable from '@loadable/component';
|
import loadable from '@loadable/component';
|
||||||
|
import MetaTracking from "../helpers/MetaTracking";
|
||||||
const AuthBasicLayout = loadable(() => import('./JKAuthBasicLayout'));
|
const AuthBasicLayout = loadable(() => import('./JKAuthBasicLayout'));
|
||||||
const PublicLayout = loadable(() => import('./JKPublicLayout'));
|
const PublicLayout = loadable(() => import('./JKPublicLayout'));
|
||||||
|
|
||||||
|
|
@ -15,6 +16,7 @@ const Layout = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
AuthBasicLayout.preload();
|
AuthBasicLayout.preload();
|
||||||
PublicLayout.preload();
|
PublicLayout.preload();
|
||||||
|
MetaTracking.init();
|
||||||
|
|
||||||
//see if there is affiliate in query string and save it as cookie
|
//see if there is affiliate in query string and save it as cookie
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
|
@ -28,7 +30,7 @@ const Layout = () => {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path="/errors" component={ErrorLayout} />
|
<Route path="/errors" component={ErrorLayout} />
|
||||||
<Route path="/auth" component={AuthBasicLayout} />
|
<Route path="/auth" component={AuthBasicLayout} />
|
||||||
|
|
@ -37,7 +39,7 @@ const Layout = () => {
|
||||||
</Switch>
|
</Switch>
|
||||||
<ToastContainer transition={Fade} closeButton={<CloseButton />} position={toast.POSITION.BOTTOM_RIGHT} />
|
<ToastContainer transition={Fade} closeButton={<CloseButton />} position={toast.POSITION.BOTTOM_RIGHT} />
|
||||||
<BuildMeta />
|
<BuildMeta />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,14 @@
|
||||||
execute "ALTER TABLE users ADD COLUMN trial_expires_reminder1_sent_at TIMESTAMP"
|
execute "ALTER TABLE users ADD COLUMN trial_expires_reminder1_sent_at TIMESTAMP"
|
||||||
execute "ALTER TABLE users ADD COLUMN trial_expires_reminder2_sent_at TIMESTAMP"
|
execute "ALTER TABLE users ADD COLUMN trial_expires_reminder2_sent_at TIMESTAMP"
|
||||||
execute "ALTER TABLE users ADD COLUMN trial_expires_reminder3_sent_at TIMESTAMP"
|
execute "ALTER TABLE users ADD COLUMN trial_expires_reminder3_sent_at TIMESTAMP"
|
||||||
end
|
|
||||||
|
# slide in some more production indexes
|
||||||
|
execute "CREATE INDEX index_users_on_first_music_session_at ON users USING btree (first_music_session_at)"
|
||||||
|
# subscription_sync_code
|
||||||
|
execute "CREATE INDEX index_users_on_subscription_sync_code ON users USING btree (subscription_sync_code)"
|
||||||
|
# first_certified_gear_at
|
||||||
|
execute "CREATE INDEX index_users_on_first_certified_gear_at ON users USING btree (first_certified_gear_at)"
|
||||||
|
end
|
||||||
def self.down
|
def self.down
|
||||||
execute "ALTER TABLE users DROP COLUMN trial_expires_reminder1_sent_at"
|
execute "ALTER TABLE users DROP COLUMN trial_expires_reminder1_sent_at"
|
||||||
execute "ALTER TABLE users DROP COLUMN trial_expires_reminder2_sent_at"
|
execute "ALTER TABLE users DROP COLUMN trial_expires_reminder2_sent_at"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
class AddFacebookTrackingToUsers < ActiveRecord::Migration
|
||||||
|
def up
|
||||||
|
execute "ALTER TABLE users ADD COLUMN facebook_click_id varchar(1000)"
|
||||||
|
execute "ALTER TABLE users ADD COLUMN facebook_browser_id varchar(1000)"
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
execute "ALTER TABLE users DROP COLUMN facebook_click_id"
|
||||||
|
execute "ALTER TABLE users DROP COLUMN facebook_browser_id"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
class AddExtendedUtmToUsers < ActiveRecord::Migration[5.0]
|
||||||
|
def up
|
||||||
|
execute <<-SQL
|
||||||
|
ALTER TABLE users ADD COLUMN origin_id character varying;
|
||||||
|
ALTER TABLE users ADD COLUMN origin_term character varying;
|
||||||
|
ALTER TABLE users ADD COLUMN origin_content character varying;
|
||||||
|
|
||||||
|
CREATE INDEX index_users_on_origin_id ON users (origin_id);
|
||||||
|
CREATE INDEX index_users_on_origin_term ON users (origin_term);
|
||||||
|
CREATE INDEX index_users_on_origin_content ON users (origin_content);
|
||||||
|
|
||||||
|
CREATE INDEX index_users_on_origin_utm_source ON users (origin_utm_source);
|
||||||
|
CREATE INDEX index_users_on_origin_utm_medium ON users (origin_utm_medium);
|
||||||
|
SQL
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
execute <<-SQL
|
||||||
|
DROP INDEX IF EXISTS index_users_on_origin_utm_medium;
|
||||||
|
DROP INDEX IF EXISTS index_users_on_origin_utm_source;
|
||||||
|
|
||||||
|
DROP INDEX IF EXISTS index_users_on_origin_content;
|
||||||
|
DROP INDEX IF EXISTS index_users_on_origin_term;
|
||||||
|
DROP INDEX IF EXISTS index_users_on_origin_id;
|
||||||
|
|
||||||
|
ALTER TABLE users DROP COLUMN IF EXISTS origin_content;
|
||||||
|
ALTER TABLE users DROP COLUMN IF EXISTS origin_term;
|
||||||
|
ALTER TABLE users DROP COLUMN IF EXISTS origin_id;
|
||||||
|
SQL
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -56,6 +56,7 @@ require "jam_ruby/lib/em_helper"
|
||||||
require "jam_ruby/lib/nav"
|
require "jam_ruby/lib/nav"
|
||||||
require "jam_ruby/lib/html_sanitize"
|
require "jam_ruby/lib/html_sanitize"
|
||||||
require "jam_ruby/lib/guitar_center"
|
require "jam_ruby/lib/guitar_center"
|
||||||
|
require "jam_ruby/lib/capi_transmitter"
|
||||||
require "jam_ruby/subscription_definitions"
|
require "jam_ruby/subscription_definitions"
|
||||||
require "jam_ruby/resque/resque_jam_error"
|
require "jam_ruby/resque/resque_jam_error"
|
||||||
require "jam_ruby/resque/resque_hooks"
|
require "jam_ruby/resque/resque_hooks"
|
||||||
|
|
|
||||||
|
|
@ -436,7 +436,7 @@ module JamRuby
|
||||||
sendgrid_substitute('@USERID', [user.id])
|
sendgrid_substitute('@USERID', [user.id])
|
||||||
sendgrid_unique_args :type => "profile_complete_reminder2"
|
sendgrid_unique_args :type => "profile_complete_reminder2"
|
||||||
|
|
||||||
mail(:to => user.email, :subject => "Take 2 minutes to fill out your JamKazam profile now") do |format|
|
mail(:to => user.email, :subject => I18n.t('user_mailer.profile_complete_reminder2.subject')) do |format|
|
||||||
format.text
|
format.text
|
||||||
format.html { render layout: "user_mailer_beta" }
|
format.html { render layout: "user_mailer_beta" }
|
||||||
end
|
end
|
||||||
|
|
@ -448,7 +448,7 @@ module JamRuby
|
||||||
sendgrid_substitute('@USERID', [user.id])
|
sendgrid_substitute('@USERID', [user.id])
|
||||||
sendgrid_unique_args :type => "profile_complete_reminder3"
|
sendgrid_unique_args :type => "profile_complete_reminder3"
|
||||||
|
|
||||||
mail(:to => user.email, :subject => "Last reminder to update your JamKazam profile") do |format|
|
mail(:to => user.email, :subject => I18n.t('user_mailer.profile_complete_reminder3.subject')) do |format|
|
||||||
format.text
|
format.text
|
||||||
format.html { render layout: "user_mailer_beta" }
|
format.html { render layout: "user_mailer_beta" }
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
require 'net/http'
|
||||||
|
require 'uri'
|
||||||
|
require 'json'
|
||||||
|
|
||||||
|
class CapiTransmitter
|
||||||
|
|
||||||
|
def self.send_event(event_name, user, custom_data = {}, event_source_url = nil, test_event_code = nil)
|
||||||
|
begin
|
||||||
|
puts("CapiTransmitter: Sending event #{event_name} for User #{user.id}")
|
||||||
|
pixel_id = APP_CONFIG.facebook_pixel_id
|
||||||
|
access_token = APP_CONFIG.facebook_access_token
|
||||||
|
|
||||||
|
if pixel_id.blank? || access_token.blank?
|
||||||
|
puts("CapiTransmitter: Missing Facebook Pixel ID or Access Token. Skipping event #{event_name}.")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
# Construct the User Data object
|
||||||
|
user_data = {
|
||||||
|
# client_ip_address: user.current_sign_in_ip,
|
||||||
|
# client_user_agent: user.user_agent # Note: User model might not have user_agent stored directly, might need to pass it or rely on what's available
|
||||||
|
}
|
||||||
|
|
||||||
|
if !user.facebook_click_id.present? ||
|
||||||
|
!APP_CONFIG.facebook_ad_source.include?(user.origin_utm_source)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# Enhance user data with hashed PII if available
|
||||||
|
# Facebook requires SHA256 hashing for PII
|
||||||
|
# For now, we rely on click_id and browser_id as primary keys if available
|
||||||
|
user_data[:fbc] = user.facebook_click_id if user.facebook_click_id.present?
|
||||||
|
user_data[:fbp] = user.facebook_browser_id if user.facebook_browser_id.present?
|
||||||
|
|
||||||
|
# If we have email/phone, we should hash them. But for now, let's stick to the IDs.
|
||||||
|
# Ideally we should hash email if we have it.
|
||||||
|
if user.email.present?
|
||||||
|
user_data[:em] = Digest::SHA256.hexdigest(user.email.downcase)
|
||||||
|
end
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
event_name: event_name,
|
||||||
|
event_time: Time.now.to_i,
|
||||||
|
action_source: "website",
|
||||||
|
user_data: user_data,
|
||||||
|
custom_data: custom_data,
|
||||||
|
event_source_url: event_source_url
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
if test_event_code.present?
|
||||||
|
payload[:test_event_code] = test_event_code
|
||||||
|
end
|
||||||
|
|
||||||
|
url = URI.parse("https://graph.facebook.com/v19.0/#{pixel_id}/events?access_token=#{access_token}")
|
||||||
|
|
||||||
|
http = Net::HTTP.new(url.host, url.port)
|
||||||
|
|
||||||
|
if APP_CONFIG.facebook_conversion_api_tls == false
|
||||||
|
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
||||||
|
else
|
||||||
|
http.use_ssl = true
|
||||||
|
end
|
||||||
|
|
||||||
|
request = Net::HTTP::Post.new(url.request_uri)
|
||||||
|
request['Content-Type'] = 'application/json'
|
||||||
|
request.body = payload.to_json
|
||||||
|
|
||||||
|
response = http.request(request)
|
||||||
|
|
||||||
|
if response.code.to_i >= 400
|
||||||
|
puts("CapiTransmitter Error: #{response.code} - #{response.body}")
|
||||||
|
else
|
||||||
|
puts("CapiTransmitter Success: #{event_name} sent for User #{user.id}")
|
||||||
|
end
|
||||||
|
|
||||||
|
rescue => e
|
||||||
|
puts("CapiTransmitter Exception: #{e.message}")
|
||||||
|
puts(e.backtrace.join("\n"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -4,20 +4,22 @@ module JamRuby
|
||||||
|
|
||||||
def self.send_reminders
|
def self.send_reminders
|
||||||
begin
|
begin
|
||||||
|
cutoff_date = Date.parse(Rails.application.config.profile_complete_reminders_effective_from_date) # Define a cutoff date for the profile completion emails
|
||||||
#If the user has not updated their profile 1 day after signup, then send reminder email1
|
#If the user has not updated their profile 1 day after signup, then send reminder email1
|
||||||
reminder1_users.find_each do |user|
|
reminder1_users(cutoff_date).find_each do |user|
|
||||||
|
puts "reminder1_users user: #{user.id}"
|
||||||
UserMailer.profile_complete_reminder1(user).deliver_now
|
UserMailer.profile_complete_reminder1(user).deliver_now
|
||||||
User.where(id: user.id).update_all(profile_complete_reminder1_sent_at: Time.now)
|
User.where(id: user.id).update_all(profile_complete_reminder1_sent_at: Time.now)
|
||||||
end
|
end
|
||||||
|
|
||||||
#If the user has not updated their profile 3 days after signup, then send reminder email2
|
#If the user has not updated their profile 3 days after signup, then send reminder email2
|
||||||
reminder2_users.find_each do |user|
|
reminder2_users(cutoff_date).find_each do |user|
|
||||||
UserMailer.profile_complete_reminder2(user).deliver_now
|
UserMailer.profile_complete_reminder2(user).deliver_now
|
||||||
User.where(id: user.id).update_all(profile_complete_reminder2_sent_at: Time.now)
|
User.where(id: user.id).update_all(profile_complete_reminder2_sent_at: Time.now)
|
||||||
end
|
end
|
||||||
|
|
||||||
#If the user has not updated their profile 5 days after signup, then send reminder email3
|
#If the user has not updated their profile 5 days after signup, then send reminder email3
|
||||||
reminder3_users.find_each do |user|
|
reminder3_users(cutoff_date).find_each do |user|
|
||||||
UserMailer.profile_complete_reminder3(user).deliver_now
|
UserMailer.profile_complete_reminder3(user).deliver_now
|
||||||
User.where(id: user.id).update_all(profile_complete_reminder3_sent_at: Time.now)
|
User.where(id: user.id).update_all(profile_complete_reminder3_sent_at: Time.now)
|
||||||
end
|
end
|
||||||
|
|
@ -31,18 +33,20 @@ module JamRuby
|
||||||
User.where("users.profile_completed_at IS NULL AND users.subscribe_email = ?", true)
|
User.where("users.profile_completed_at IS NULL AND users.subscribe_email = ?", true)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.reminder1_users
|
def self.reminder1_users(cutoff_date)
|
||||||
EmailProfileReminder.prospect_users.where("users.created_at < ? AND users.profile_complete_reminder1_sent_at IS NULL", 1.day.ago)
|
# ensure that the user has had the account for at least one day
|
||||||
|
puts "reminder1_users cutoff_date: #{cutoff_date}"
|
||||||
|
EmailProfileReminder.prospect_users.where("users.created_at > ? AND users.profile_complete_reminder1_sent_at IS NULL AND (NOW() - users.created_at) > INTERVAL '1 day'", cutoff_date)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.reminder2_users
|
def self.reminder2_users(cutoff_date)
|
||||||
EmailProfileReminder.prospect_users.where("users.created_at < ? AND users.profile_complete_reminder1_sent_at IS NOT NULL AND users.profile_complete_reminder2_sent_at IS NULL", 3.days.ago)
|
# ensure that the user has had the account for at least three days and guard against rapid back-to-back emails
|
||||||
|
EmailProfileReminder.prospect_users.where("users.created_at > ? AND users.profile_complete_reminder1_sent_at < ? AND users.profile_complete_reminder1_sent_at IS NOT NULL AND users.profile_complete_reminder2_sent_at IS NULL", cutoff_date, 2.days.ago)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.reminder3_users
|
def self.reminder3_users(cutoff_date)
|
||||||
EmailProfileReminder.prospect_users.where("users.created_at < ? AND users.profile_complete_reminder2_sent_at IS NOT NULL AND users.profile_complete_reminder3_sent_at IS NULL", 5.days.ago)
|
# ensure that user has had the profile for 5 days and guard against rapid back-to-back emails
|
||||||
|
EmailProfileReminder.prospect_users.where("users.created_at > ? AND users.profile_complete_reminder2_sent_at < ? AND users.profile_complete_reminder2_sent_at IS NOT NULL AND users.profile_complete_reminder3_sent_at IS NULL", cutoff_date, 2.days.ago)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -28,19 +28,19 @@ module JamRuby
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.prospect_users
|
def self.prospect_users
|
||||||
User.where("users.first_certified_gear_at IS NULL")
|
User.where("users.first_certified_gear_at IS NULL AND users.subscribe_email = ?", true)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.reminder1_users(cutoff_date)
|
def self.reminder1_users(cutoff_date)
|
||||||
GearSetupReminder.prospect_users.where("users.created_at < ? AND users.created_at > ? AND users.gear_setup_reminder1_sent_at IS NULL", 1.day.ago, cutoff_date)
|
GearSetupReminder.prospect_users.where("users.created_at > ? AND users.gear_setup_reminder1_sent_at IS NULL AND (NOW() - users.created_at) > INTERVAL '1 day'", cutoff_date)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.reminder2_users(cutoff_date)
|
def self.reminder2_users(cutoff_date)
|
||||||
GearSetupReminder.prospect_users.where("users.created_at < ? AND users.created_at > ? AND users.gear_setup_reminder1_sent_at IS NOT NULL AND users.gear_setup_reminder2_sent_at IS NULL", 3.days.ago, cutoff_date)
|
GearSetupReminder.prospect_users.where("users.created_at > ? AND users.gear_setup_reminder1_sent_at IS NOT NULL AND users.gear_setup_reminder2_sent_at IS NULL AND users.gear_setup_reminder1_sent_at < ?", cutoff_date, 2.days.ago)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.reminder3_users(cutoff_date)
|
def self.reminder3_users(cutoff_date)
|
||||||
GearSetupReminder.prospect_users.where("users.created_at < ? AND users.created_at > ? AND users.gear_setup_reminder2_sent_at IS NOT NULL AND users.gear_setup_reminder3_sent_at IS NULL", 5.days.ago, cutoff_date)
|
GearSetupReminder.prospect_users.where("users.created_at > ? AND users.gear_setup_reminder2_sent_at IS NOT NULL AND users.gear_setup_reminder3_sent_at IS NULL AND users.gear_setup_reminder2_sent_at < ?", cutoff_date, 2.days.ago)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -36,11 +36,11 @@ module JamRuby
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.reminder2_users(cutoff_date)
|
def self.reminder2_users(cutoff_date)
|
||||||
GroupSessionReminder.prospect_users.where("users.created_at > ? AND users.group_session_reminder1_sent_at IS NOT NULL AND users.group_session_reminder2_sent_at IS NULL AND users.first_music_session_at < ?", cutoff_date, 3.days.ago)
|
GroupSessionReminder.prospect_users.where("users.created_at > ? AND users.group_session_reminder1_sent_at IS NOT NULL AND users.group_session_reminder2_sent_at IS NULL AND users.first_music_session_at < ? AND group_session_reminder1_sent_at < ?", cutoff_date, 3.days.ago, 1.day.ago)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.reminder3_users(cutoff_date)
|
def self.reminder3_users(cutoff_date)
|
||||||
GroupSessionReminder.prospect_users.where("users.created_at > ? AND users.group_session_reminder2_sent_at IS NOT NULL AND users.group_session_reminder3_sent_at IS NULL AND users.first_music_session_at < ?", cutoff_date, 5.days.ago)
|
GroupSessionReminder.prospect_users.where("users.created_at > ? AND users.group_session_reminder2_sent_at IS NOT NULL AND users.group_session_reminder3_sent_at IS NULL AND users.first_music_session_at < ? AND group_session_reminder2_sent_at < ?", cutoff_date, 5.days.ago, 1.day.ago)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -27,19 +27,19 @@ module JamRuby
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.prospect_users
|
def self.prospect_users
|
||||||
User.where("users.first_music_session_at IS NULL")
|
User.where("users.first_music_session_at IS NULL AND first_certified_gear_at IS NOT NULL")
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.reminder1_users(cutoff_date)
|
def self.reminder1_users(cutoff_date)
|
||||||
TestGearReminder.prospect_users.where("users.first_certified_gear_at < ? AND users.created_at >= ? AND users.test_gear_reminder1_sent_at IS NULL", 1.day.ago, cutoff_date)
|
TestGearReminder.prospect_users.where("users.created_at > ? AND users.test_gear_reminder1_sent_at IS NULL AND (NOW() - users.first_certified_gear_at) > INTERVAL '1 day'", cutoff_date)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.reminder2_users(cutoff_date)
|
def self.reminder2_users(cutoff_date)
|
||||||
TestGearReminder.prospect_users.where("users.first_certified_gear_at < ? AND users.created_at >= ? AND users.test_gear_reminder1_sent_at IS NOT NULL AND users.test_gear_reminder2_sent_at IS NULL", 3.days.ago, cutoff_date)
|
TestGearReminder.prospect_users.where("users.created_at > ? AND users.test_gear_reminder1_sent_at IS NOT NULL AND users.test_gear_reminder2_sent_at IS NULL AND users.test_gear_reminder1_sent_at < ?", cutoff_date, 2.days.ago)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.reminder3_users(cutoff_date)
|
def self.reminder3_users(cutoff_date)
|
||||||
TestGearReminder.prospect_users.where("users.first_certified_gear_at < ? AND users.created_at > ? AND users.test_gear_reminder2_sent_at IS NOT NULL AND users.test_gear_reminder3_sent_at IS NULL", 5.days.ago, cutoff_date)
|
TestGearReminder.prospect_users.where("users.created_at > ? AND users.test_gear_reminder2_sent_at IS NOT NULL AND users.test_gear_reminder3_sent_at IS NULL AND users.test_gear_reminder2_sent_at < ?", cutoff_date, 2.days.ago)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -8,24 +8,26 @@ module JamRuby
|
||||||
class TrialExpiresReminder
|
class TrialExpiresReminder
|
||||||
@@log = Logging.logger[TrialExpiresReminder]
|
@@log = Logging.logger[TrialExpiresReminder]
|
||||||
|
|
||||||
FIRST_NOTIFICATION_CHECK = 1.day.ago.to_s
|
FIRST_NOTIFICATION_CHECK = 1.day.ago
|
||||||
SECOND_NOTIFICATION_CHECK = 5.days.ago.to_s
|
# unused here, but just as a form of simple documentation...
|
||||||
THIRD_NOTIFICATION_CHECK = 9.days.ago
|
# SECOND_NOTIFICATION_CHECK = 4.days (from the previous reminder)
|
||||||
|
# THIRD_NOTIFICATION_CHECK = 4.days (from the previous reminder)
|
||||||
|
|
||||||
def self.prospect_users(cutoff_date)
|
def self.prospect_users(cutoff_date)
|
||||||
User.where("(users.subscription_trial_ends_at IS NOT NULL AND users.subscription_trial_ends_at > ?)", cutoff_date)
|
User.where("(users.subscription_trial_ends_at IS NOT NULL AND users.created_at > ?)", cutoff_date)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# trial_ended | in_trial
|
||||||
def self.reminder1_users(cutoff_date)
|
def self.reminder1_users(cutoff_date)
|
||||||
prospect_users(cutoff_date).where("users.subscription_last_checked_at < ? AND users.subscription_trial_ends_at < ? AND users.trial_expires_reminder1_sent_at IS NULL", 1.day.ago, FIRST_NOTIFICATION_CHECK)
|
prospect_users(cutoff_date).where("users.subscription_sync_code = 'trial_ended' AND users.subscription_trial_ends_at < ? AND users.trial_expires_reminder1_sent_at IS NULL", FIRST_NOTIFICATION_CHECK)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.reminder2_users(cutoff_date)
|
def self.reminder2_users(cutoff_date)
|
||||||
prospect_users(cutoff_date).where("users.subscription_last_checked_at < ? AND users.subscription_trial_ends_at < ? AND trial_expires_reminder1_sent_at IS NOT NULL AND users.trial_expires_reminder2_sent_at IS NULL", 1.day.ago, SECOND_NOTIFICATION_CHECK)
|
prospect_users(cutoff_date).where("users.subscription_sync_code = 'trial_ended' AND trial_expires_reminder1_sent_at IS NOT NULL AND users.trial_expires_reminder2_sent_at IS NULL AND (NOW() - trial_expires_reminder1_sent_at) > INTERVAL '4 days'")
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.reminder3_users(cutoff_date)
|
def self.reminder3_users(cutoff_date)
|
||||||
prospect_users(cutoff_date).where("users.subscription_last_checked_at < ? AND users.subscription_trial_ends_at < ? AND trial_expires_reminder2_sent_at IS NOT NULL AND users.trial_expires_reminder3_sent_at IS NULL", 1.day.ago, THIRD_NOTIFICATION_CHECK)
|
prospect_users(cutoff_date).where("users.subscription_sync_code = 'trial_ended' AND trial_expires_reminder2_sent_at IS NOT NULL AND users.trial_expires_reminder3_sent_at IS NULL AND (NOW() - trial_expires_reminder2_sent_at) > INTERVAL '4 days'")
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.send_reminders
|
def self.send_reminders
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
|
|
||||||
module JamRuby
|
module JamRuby
|
||||||
class Ars < ActiveRecord::Base
|
class Ars < ActiveRecord::Base
|
||||||
|
|
||||||
attr_accessible :active, :name, :id_int, :ip, as: :admin
|
attr_accessible :password, :username, :active, :beta, :name, :provider, :id_int, :ip, :port, :continent, :country, :city, :subdivision, :latitude, :longitude, as: :admin
|
||||||
|
|
||||||
self.table_name = "arses"
|
self.table_name = "arses"
|
||||||
@@log = Logging.logger[Ars]
|
@@log = Logging.logger[Ars]
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ module JamRuby
|
||||||
after_save :update_teacher_pct
|
after_save :update_teacher_pct
|
||||||
|
|
||||||
attr_accessible :first_name, :last_name, :email, :city, :password, :password_confirmation, :state, :country, :birth_date, :subscribe_email, :terms_of_service, :original_fpfile, :cropped_fpfile, :cropped_large_fpfile, :cropped_s3_path, :cropped_large_s3_path, :photo_url, :large_photo_url, :crop_selection, :used_current_month, :used_month_play_time,
|
attr_accessible :first_name, :last_name, :email, :city, :password, :password_confirmation, :state, :country, :birth_date, :subscribe_email, :terms_of_service, :original_fpfile, :cropped_fpfile, :cropped_large_fpfile, :cropped_s3_path, :cropped_large_s3_path, :photo_url, :large_photo_url, :crop_selection, :used_current_month, :used_month_play_time,
|
||||||
:v2_photo_url, :v2_photo_uploaded
|
:v2_photo_url, :v2_photo_uploaded, :facebook_click_id, :facebook_browser_id
|
||||||
|
|
||||||
# updating_password corresponds to a lost_password
|
# updating_password corresponds to a lost_password
|
||||||
attr_accessor :test_drive_packaging, :validate_instruments, :updating_password, :updating_email, :updated_email, :update_email_confirmation_url, :administratively_created, :current_password, :setting_password, :confirm_current_password, :updating_avatar, :updating_progression_field, :mods_json, :expecting_gift_card, :purchase_required, :user_type
|
attr_accessor :test_drive_packaging, :validate_instruments, :updating_password, :updating_email, :updated_email, :update_email_confirmation_url, :administratively_created, :current_password, :setting_password, :confirm_current_password, :updating_avatar, :updating_progression_field, :mods_json, :expecting_gift_card, :purchase_required, :user_type
|
||||||
|
|
@ -542,6 +542,21 @@ module JamRuby
|
||||||
@updating_progression_field = true
|
@updating_progression_field = true
|
||||||
if self[field_name].nil?
|
if self[field_name].nil?
|
||||||
self[field_name] = time
|
self[field_name] = time
|
||||||
|
|
||||||
|
# CAPI Hooks
|
||||||
|
begin
|
||||||
|
case field_name.to_s
|
||||||
|
when 'first_ran_client_at'
|
||||||
|
# StartTrial: When user opens the app
|
||||||
|
CapiTransmitter.send_event('StartTrial', self, { value: '0.00', currency: 'USD', predicted_ltv: '0.00' })
|
||||||
|
when 'first_certified_gear_at'
|
||||||
|
# AddToCart: When user finishes Gear Wizard
|
||||||
|
CapiTransmitter.send_event('AddToCart', self)
|
||||||
|
end
|
||||||
|
rescue => e
|
||||||
|
puts("Error sending #{field_name} CAPI event: #{e.message}")
|
||||||
|
end
|
||||||
|
|
||||||
self.save
|
self.save
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -1579,6 +1594,8 @@ module JamRuby
|
||||||
license_end = options[:license_end]
|
license_end = options[:license_end]
|
||||||
import_source = options[:import_source]
|
import_source = options[:import_source]
|
||||||
desired_plan_code = options[:desired_plan_code]
|
desired_plan_code = options[:desired_plan_code]
|
||||||
|
facebook_click_id = options[:facebook_click_id]
|
||||||
|
facebook_browser_id = options[:facebook_browser_id]
|
||||||
|
|
||||||
if desired_plan_code == ''
|
if desired_plan_code == ''
|
||||||
desired_plan_code = nil
|
desired_plan_code = nil
|
||||||
|
|
@ -1640,16 +1657,24 @@ module JamRuby
|
||||||
musician = true
|
musician = true
|
||||||
end
|
end
|
||||||
user.musician = !!musician
|
user.musician = !!musician
|
||||||
|
user.facebook_click_id = facebook_click_id
|
||||||
|
user.facebook_browser_id = facebook_browser_id
|
||||||
|
|
||||||
if origin
|
if origin
|
||||||
user.origin_utm_source = origin["utm_source"]
|
user.origin_utm_source = origin["utm_source"]
|
||||||
user.origin_utm_medium = origin["utm_medium"]
|
user.origin_utm_medium = origin["utm_medium"]
|
||||||
user.origin_utm_campaign = origin["utm_campaign"]
|
user.origin_utm_campaign = origin["utm_campaign"]
|
||||||
|
user.origin_id = origin["utm_id"]
|
||||||
|
user.origin_term = origin["utm_term"]
|
||||||
|
user.origin_content = origin["utm_content"]
|
||||||
user.origin_referrer = origin["referrer"]
|
user.origin_referrer = origin["referrer"]
|
||||||
else
|
else
|
||||||
user.origin_utm_source = 'organic'
|
user.origin_utm_source = 'organic'
|
||||||
user.origin_utm_medium = 'organic'
|
user.origin_utm_medium = 'organic'
|
||||||
user.origin_utm_campaign = nil
|
user.origin_utm_campaign = nil
|
||||||
|
user.origin_id = nil
|
||||||
|
user.origin_term = nil
|
||||||
|
user.origin_content = nil
|
||||||
user.origin_referrer = nil
|
user.origin_referrer = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -1865,6 +1890,15 @@ module JamRuby
|
||||||
if user.errors.any?
|
if user.errors.any?
|
||||||
raise ActiveRecord::Rollback
|
raise ActiveRecord::Rollback
|
||||||
else
|
else
|
||||||
|
# CAPI Hook: CompleteRegistration
|
||||||
|
if user.facebook_click_id.present?
|
||||||
|
begin
|
||||||
|
CapiTransmitter.send_event('CompleteRegistration', user)
|
||||||
|
rescue => e
|
||||||
|
puts("Error sending CompleteRegistration CAPI event: #{e.message}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# 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 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?
|
if affiliate_partner && affiliate_partner.partner_user.nil?
|
||||||
affiliate_partner.partner_user = user
|
affiliate_partner.partner_user = user
|
||||||
|
|
|
||||||
|
|
@ -336,6 +336,33 @@ module JamRuby
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def report_meta_capi(current_user, plan_code)
|
||||||
|
# CAPI Hook: Subscribe
|
||||||
|
begin
|
||||||
|
monthly_cost = case plan_code
|
||||||
|
when JamRuby::SubscriptionDefinitions::JAM_SILVER, JamRuby::SubscriptionDefinitions::JAM_SILVER_YEARLY
|
||||||
|
5.00
|
||||||
|
when JamRuby::SubscriptionDefinitions::JAM_GOLD, JamRuby::SubscriptionDefinitions::JAM_GOLD_YEARLY
|
||||||
|
10.00
|
||||||
|
when JamRuby::SubscriptionDefinitions::JAM_PLATINUM, JamRuby::SubscriptionDefinitions::JAM_PLATINUM_YEARLY
|
||||||
|
20.00
|
||||||
|
else
|
||||||
|
0.00
|
||||||
|
end
|
||||||
|
|
||||||
|
ltv = monthly_cost * 12
|
||||||
|
begin
|
||||||
|
puts "Sending CAPI Subscribe event #{current_user.email}, #{monthly_cost}, #{ltv}"
|
||||||
|
CapiTransmitter.send_event('Subscribe', current_user, { value: monthly_cost.to_s, currency: 'USD', predicted_ltv: ltv.to_s })
|
||||||
|
rescue => e
|
||||||
|
puts "Error sending CAPI Subscribe event #{current_user.email}, #{e.message}"
|
||||||
|
end
|
||||||
|
|
||||||
|
rescue => e
|
||||||
|
puts "Error sending CAPI Subscribe event #{current_user.email}, #{e.message}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def get_highest_plan(subscription)
|
def get_highest_plan(subscription)
|
||||||
SubscriptionDefinitions.higher_plan(subscription.plan.plan_code, get_pending_plan_code(subscription))
|
SubscriptionDefinitions.higher_plan(subscription.plan.plan_code, get_pending_plan_code(subscription))
|
||||||
end
|
end
|
||||||
|
|
@ -364,6 +391,8 @@ module JamRuby
|
||||||
end
|
end
|
||||||
current_user.reset_playtime
|
current_user.reset_playtime
|
||||||
current_user.save(validate: false)
|
current_user.save(validate: false)
|
||||||
|
|
||||||
|
report_meta_capi(current_user, plan_code)
|
||||||
rescue => e
|
rescue => e
|
||||||
puts "Could not create subscription for user #{current_user.email}. #{e}"
|
puts "Could not create subscription for user #{current_user.email}. #{e}"
|
||||||
return false, subscription, account
|
return false, subscription, account
|
||||||
|
|
|
||||||
|
|
@ -19,13 +19,19 @@ describe TrialExpiresReminder do
|
||||||
ActionMailer::Base.deliveries.clear
|
ActionMailer::Base.deliveries.clear
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def no_more_emails_sent
|
||||||
|
UserMailer.deliveries.clear
|
||||||
|
TrialExpiresReminder.send_reminders
|
||||||
|
expect(ActionMailer::Base.deliveries.count).to eq(0)
|
||||||
|
end
|
||||||
|
|
||||||
it "sends reminder emails to users whose trials are about to expire" do
|
it "sends reminder emails to users whose trials are about to expire" do
|
||||||
user1.subscription_trial_ends_at = 1.days.from_now
|
user1.subscription_trial_ends_at = 1.days.from_now
|
||||||
user1.subscription_last_checked_at = 2.days.ago
|
user1.subscription_sync_code = 'trial_ended'
|
||||||
user1.save!
|
user1.save!
|
||||||
|
|
||||||
user2.subscription_trial_ends_at = 1.days.ago
|
user2.subscription_trial_ends_at = 2.days.ago
|
||||||
user2.subscription_last_checked_at = 2.days.ago
|
user2.subscription_sync_code = 'trial_ended'
|
||||||
user2.save!
|
user2.save!
|
||||||
|
|
||||||
TrialExpiresReminder.send_reminders
|
TrialExpiresReminder.send_reminders
|
||||||
|
|
@ -36,11 +42,14 @@ describe TrialExpiresReminder do
|
||||||
expect(ActionMailer::Base.deliveries.last.subject).to include("Your free gold trial has expired, but you have great options to keep playing!")
|
expect(ActionMailer::Base.deliveries.last.subject).to include("Your free gold trial has expired, but you have great options to keep playing!")
|
||||||
|
|
||||||
expect(user2.reload.trial_expires_reminder1_sent_at).not_to be_nil
|
expect(user2.reload.trial_expires_reminder1_sent_at).not_to be_nil
|
||||||
|
|
||||||
|
no_more_emails_sent
|
||||||
end
|
end
|
||||||
|
|
||||||
it "does not send reminder emails to users who have already received them" do
|
it "does not send reminder emails to users who have already received them" do
|
||||||
user1.subscription_trial_ends_at = 1.days.ago
|
user1.reload
|
||||||
user1.subscription_last_checked_at = 2.days.ago
|
user1.subscription_trial_ends_at = 1.days.ago + 1.hour
|
||||||
|
user1.subscription_sync_code = 'trial_ended'
|
||||||
user1.trial_expires_reminder1_sent_at = Time.now
|
user1.trial_expires_reminder1_sent_at = Time.now
|
||||||
user1.save!
|
user1.save!
|
||||||
|
|
||||||
|
|
@ -50,31 +59,38 @@ describe TrialExpiresReminder do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "sends the second reminder email to users whose trials are about to expire" do
|
it "sends the second reminder email to users whose trials are about to expire" do
|
||||||
user1.subscription_trial_ends_at = 4.days.ago
|
user1.reload
|
||||||
user1.subscription_last_checked_at = 1.days.ago
|
user2.reload
|
||||||
user1.trial_expires_reminder1_sent_at = Time.now
|
|
||||||
|
# pretend that the first reminder email was sent 2 days ago
|
||||||
|
user1.subscription_trial_ends_at = 4.days.ago + 1.hour
|
||||||
|
user1.subscription_sync_code = 'trial_ended'
|
||||||
|
user1.trial_expires_reminder1_sent_at = 5.days.ago
|
||||||
user1.save!
|
user1.save!
|
||||||
|
|
||||||
user2.subscription_trial_ends_at = 5.days.ago
|
user2.subscription_trial_ends_at = 4.days.ago + 1.hour
|
||||||
user2.subscription_last_checked_at = 1.days.ago
|
user2.subscription_sync_code = 'trial_ended'
|
||||||
user2.trial_expires_reminder1_sent_at = Time.now
|
user2.trial_expires_reminder1_sent_at = Time.now
|
||||||
user2.save!
|
user2.save!
|
||||||
|
|
||||||
TrialExpiresReminder.send_reminders
|
TrialExpiresReminder.send_reminders
|
||||||
|
|
||||||
expect(ActionMailer::Base.deliveries.count).to eq(1)
|
expect(ActionMailer::Base.deliveries.count).to eq(1)
|
||||||
expect(ActionMailer::Base.deliveries.map(&:to).flatten).to include(user2.email)
|
expect(ActionMailer::Base.deliveries.map(&:to).flatten).to include(user1.email)
|
||||||
# Check if the second reminder email was sent by verifying the subject
|
# Check if the second reminder email was sent by verifying the subject
|
||||||
expect(ActionMailer::Base.deliveries.last.subject).to include("Don’t forget to check your options to keep playing")
|
expect(ActionMailer::Base.deliveries.last.subject).to include("Don’t forget to check your options to keep playing")
|
||||||
|
|
||||||
expect(user2.reload.trial_expires_reminder2_sent_at).not_to be_nil
|
expect(user1.reload.trial_expires_reminder2_sent_at).not_to be_nil
|
||||||
|
|
||||||
|
no_more_emails_sent
|
||||||
end
|
end
|
||||||
|
|
||||||
it "sends the third reminder email to users whose trials are about to expire" do
|
it "sends the third reminder email to users whose trials are about to expire" do
|
||||||
user1.subscription_trial_ends_at = 10.days.ago
|
user1.reload
|
||||||
user1.subscription_last_checked_at = 1.days.ago
|
user1.subscription_trial_ends_at = 10.days.ago + 1.hour
|
||||||
user1.trial_expires_reminder1_sent_at = 6.days.ago
|
user1.subscription_sync_code = 'trial_ended'
|
||||||
user1.trial_expires_reminder2_sent_at = 4.days.ago
|
user1.trial_expires_reminder1_sent_at = 8.days.ago
|
||||||
|
user1.trial_expires_reminder2_sent_at = 9.days.ago
|
||||||
user1.save!
|
user1.save!
|
||||||
|
|
||||||
TrialExpiresReminder.send_reminders
|
TrialExpiresReminder.send_reminders
|
||||||
|
|
@ -85,22 +101,28 @@ describe TrialExpiresReminder do
|
||||||
expect(ActionMailer::Base.deliveries.last.subject).to include("One last reminder!")
|
expect(ActionMailer::Base.deliveries.last.subject).to include("One last reminder!")
|
||||||
|
|
||||||
expect(user1.reload.trial_expires_reminder3_sent_at).not_to be_nil
|
expect(user1.reload.trial_expires_reminder3_sent_at).not_to be_nil
|
||||||
|
|
||||||
|
no_more_emails_sent
|
||||||
end
|
end
|
||||||
|
|
||||||
it "sends first and second and third reminder emails to users whose trials are about to expire" do
|
it "sends first and second and third reminder emails to users whose trials are about to expire" do
|
||||||
|
user1.reload
|
||||||
|
user2.reload
|
||||||
|
user3.reload
|
||||||
|
|
||||||
user1.subscription_trial_ends_at = 2.days.ago
|
user1.subscription_trial_ends_at = 2.days.ago
|
||||||
user1.subscription_last_checked_at = 1.days.ago
|
user1.subscription_sync_code = 'trial_ended'
|
||||||
user1.save!
|
user1.save!
|
||||||
|
|
||||||
user2.subscription_trial_ends_at = 6.days.ago
|
user2.subscription_trial_ends_at = 2.days.ago
|
||||||
user2.subscription_last_checked_at = 1.days.ago
|
user2.trial_expires_reminder1_sent_at = 5.days.ago
|
||||||
user2.trial_expires_reminder1_sent_at = 4.days.ago
|
user2.subscription_sync_code = 'trial_ended'
|
||||||
user2.save!
|
user2.save!
|
||||||
|
|
||||||
user3.subscription_trial_ends_at = 10.days.ago
|
user3.subscription_trial_ends_at = 2.days.ago
|
||||||
user3.subscription_last_checked_at = 2.days.ago
|
user3.trial_expires_reminder1_sent_at = 8.days.ago
|
||||||
user3.trial_expires_reminder1_sent_at = 6.days.ago
|
user3.trial_expires_reminder2_sent_at = 9.days.ago
|
||||||
user3.trial_expires_reminder2_sent_at = 4.days.ago
|
user3.subscription_sync_code = 'trial_ended'
|
||||||
user3.save!
|
user3.save!
|
||||||
|
|
||||||
TrialExpiresReminder.send_reminders
|
TrialExpiresReminder.send_reminders
|
||||||
|
|
@ -112,5 +134,6 @@ describe TrialExpiresReminder do
|
||||||
expect(ActionMailer::Base.deliveries.count).to eq(3)
|
expect(ActionMailer::Base.deliveries.count).to eq(3)
|
||||||
expect(ActionMailer::Base.deliveries.map(&:to).flatten).to include(user1.email, user2.email, user3.email)
|
expect(ActionMailer::Base.deliveries.map(&:to).flatten).to include(user1.email, user2.email, user3.email)
|
||||||
|
|
||||||
|
no_more_emails_sent
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -55,6 +55,7 @@
|
||||||
//= require AAB_message_factory
|
//= require AAB_message_factory
|
||||||
//= require jam_rest
|
//= require jam_rest
|
||||||
//= require ga
|
//= require ga
|
||||||
|
//= require meta_tracking
|
||||||
//= require utils
|
//= require utils
|
||||||
//= require subscription_utils
|
//= require subscription_utils
|
||||||
//= require profile_utils
|
//= require profile_utils
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
//= require AAB_message_factory
|
//= require AAB_message_factory
|
||||||
//= require jam_rest
|
//= require jam_rest
|
||||||
//= require ga
|
//= require ga
|
||||||
|
//= require meta_tracking
|
||||||
//= require layout
|
//= require layout
|
||||||
//= require jamkazam
|
//= require jamkazam
|
||||||
//= require utils
|
//= require utils
|
||||||
|
|
|
||||||
|
|
@ -8,5 +8,6 @@
|
||||||
//= require utils
|
//= require utils
|
||||||
//= require jam_rest
|
//= require jam_rest
|
||||||
//= require ga
|
//= require ga
|
||||||
|
//= require meta_tracking
|
||||||
//= require corp/init
|
//= require corp/init
|
||||||
//= require_directory ../corp
|
//= require_directory ../corp
|
||||||
|
|
@ -64,6 +64,7 @@
|
||||||
//= require custom_controls
|
//= require custom_controls
|
||||||
//= require jam_rest
|
//= require jam_rest
|
||||||
//= require ga
|
//= require ga
|
||||||
|
//= require meta_tracking
|
||||||
//= require session_utils
|
//= require session_utils
|
||||||
//= require recording_utils
|
//= require recording_utils
|
||||||
//= require helpBubbleHelper
|
//= require helpBubbleHelper
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@
|
||||||
//= require ui_helper
|
//= require ui_helper
|
||||||
//= require jam_rest
|
//= require jam_rest
|
||||||
//= require ga
|
//= require ga
|
||||||
|
//= require meta_tracking
|
||||||
//= require recordingModel
|
//= require recordingModel
|
||||||
//= require web/signup_helper
|
//= require web/signup_helper
|
||||||
//= require web/signin_helper
|
//= require web/signin_helper
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,117 @@
|
||||||
|
/**
|
||||||
|
* meta_tracking.js
|
||||||
|
* A standalone module to capture and persist Meta attribution signals (fbclid, _fbp) in cookies.
|
||||||
|
*
|
||||||
|
* Logic adapted for legacy environment (no React hooks).
|
||||||
|
* - Checks URL for `fbclid` and sets `_fbc` cookie.
|
||||||
|
* - Checks for `_fbp` cookie; if missing, generates and sets it.
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function (window, document) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var MetaTracking = {
|
||||||
|
init: function () {
|
||||||
|
var location = window.location;
|
||||||
|
this.handleFbc(location.search);
|
||||||
|
this.handleFbp();
|
||||||
|
this.handleUtm(location.search);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 1. Parsing and storing _fbc (Click ID)
|
||||||
|
handleFbc: function (searchParams) {
|
||||||
|
var fbclid = this.getQueryParam('fbclid', searchParams);
|
||||||
|
|
||||||
|
if (fbclid) {
|
||||||
|
var version = 'fb';
|
||||||
|
var subdomainIndex = 1; // 1 = example.com
|
||||||
|
var creationTime = new Date().getTime(); // Unix timestamp in ms
|
||||||
|
|
||||||
|
// Format: fb.1.timestamp.id
|
||||||
|
var fbcValue = version + '.' + subdomainIndex + '.' + creationTime + '.' + fbclid;
|
||||||
|
|
||||||
|
this.setCookie('_fbc', fbcValue, 90);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
handleUtm: function (searchParams) {
|
||||||
|
var self = this;
|
||||||
|
if (!searchParams) return;
|
||||||
|
|
||||||
|
// Logically, we want to capture all utm_ parameters.
|
||||||
|
// We can either iterate a list or dynamic regex.
|
||||||
|
// Given the requirement to be robust, let's look for "utm_"
|
||||||
|
|
||||||
|
var query = searchParams.substring(1); // remove '?'
|
||||||
|
var vars = query.split('&');
|
||||||
|
for (var i = 0; i < vars.length; i++) {
|
||||||
|
var pair = vars[i].split('=');
|
||||||
|
if (pair.length === 2) {
|
||||||
|
var key = decodeURIComponent(pair[0]);
|
||||||
|
var value = decodeURIComponent(pair[1]);
|
||||||
|
if (key.indexOf('utm_') === 0) {
|
||||||
|
self.setCookie(key, value, 90);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 2. Handling _fbp (Browser ID)
|
||||||
|
handleFbp: function () {
|
||||||
|
if (!this.getCookie('_fbp')) {
|
||||||
|
var version = 'fb';
|
||||||
|
var subdomainIndex = 1;
|
||||||
|
var creationTime = new Date().getTime();
|
||||||
|
var randomInt = Math.floor(Math.random() * 10000000000); // 10-digit random number
|
||||||
|
|
||||||
|
// Format: fb.1.timestamp.randomDigits
|
||||||
|
var fbpValue = version + '.' + subdomainIndex + '.' + creationTime + '.' + randomInt;
|
||||||
|
|
||||||
|
this.setCookie('_fbp', fbpValue, 90);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Helper: Get query param by name
|
||||||
|
getQueryParam: function (name, search) {
|
||||||
|
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
|
||||||
|
var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
|
||||||
|
var results = regex.exec(search);
|
||||||
|
return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
|
||||||
|
},
|
||||||
|
|
||||||
|
// Helper: Set cookie
|
||||||
|
setCookie: function (name, value, days) {
|
||||||
|
var expires = "";
|
||||||
|
if (days) {
|
||||||
|
var date = new Date();
|
||||||
|
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
||||||
|
expires = "; expires=" + date.toUTCString();
|
||||||
|
}
|
||||||
|
// Ensure path is root and domain is included if needed (defaults to current host)
|
||||||
|
document.cookie = name + "=" + (value || "") + expires + "; path=/";
|
||||||
|
},
|
||||||
|
|
||||||
|
// Helper: Get cookie
|
||||||
|
getCookie: function (name) {
|
||||||
|
var nameEQ = name + "=";
|
||||||
|
var ca = document.cookie.split(';');
|
||||||
|
for (var i = 0; i < ca.length; i++) {
|
||||||
|
var c = ca[i];
|
||||||
|
while (c.charAt(0) == ' ') c = c.substring(1, c.length);
|
||||||
|
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize on ready
|
||||||
|
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
||||||
|
MetaTracking.init();
|
||||||
|
} else {
|
||||||
|
// IE9+ support for DOMContentLoaded
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
MetaTracking.init();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
})(window, document);
|
||||||
|
|
@ -23,6 +23,7 @@
|
||||||
//= require AAB_message_factory
|
//= require AAB_message_factory
|
||||||
//= require jam_rest
|
//= require jam_rest
|
||||||
//= require ga
|
//= require ga
|
||||||
|
//= require meta_tracking
|
||||||
//= require layout
|
//= require layout
|
||||||
//= require jamkazam
|
//= require jamkazam
|
||||||
//= require utils
|
//= require utils
|
||||||
|
|
@ -37,11 +38,11 @@
|
||||||
|
|
||||||
function initializeInfluxDB() {
|
function initializeInfluxDB() {
|
||||||
window.stats = new InfluxDB({
|
window.stats = new InfluxDB({
|
||||||
"host" : gon.global.influxdb_host,
|
"host": gon.global.influxdb_host,
|
||||||
"port" : gon.global.influxdb_port,
|
"port": gon.global.influxdb_port,
|
||||||
"username" : gon.global.influxdb_username,
|
"username": gon.global.influxdb_username,
|
||||||
"password" : gon.global.influxdb_password,
|
"password": gon.global.influxdb_password,
|
||||||
"database" : gon.global.influxdb_database
|
"database": gon.global.influxdb_database
|
||||||
});
|
});
|
||||||
|
|
||||||
window.stats.write = window.stats.writePoint;
|
window.stats.write = window.stats.writePoint;
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@
|
||||||
//= require custom_controls
|
//= require custom_controls
|
||||||
//= require jam_rest
|
//= require jam_rest
|
||||||
//= require ga
|
//= require ga
|
||||||
|
//= require meta_tracking
|
||||||
//= require session_utils
|
//= require session_utils
|
||||||
//= require recording_utils
|
//= require recording_utils
|
||||||
//= require helpBubbleHelper
|
//= require helpBubbleHelper
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
class ApiJamTracksController < ApiController
|
class ApiJamTracksController < ApiController
|
||||||
|
|
||||||
# have to be signed in currently to see this screen
|
# have to be signed in currently to see this screen
|
||||||
before_filter :api_signed_in_user, :except => [:index, :autocomplete, :show_with_artist_info, :artist_index]
|
before_filter :api_signed_in_user, :except => [:index, :autocomplete, :show_with_artist_info, :artist_index, :public_show]
|
||||||
before_filter :api_any_user, :only => [:index, :autocomplete, :show_with_artist_info, :artist_index]
|
before_filter :api_any_user, :only => [:index, :autocomplete, :show_with_artist_info, :artist_index]
|
||||||
before_filter :lookup_jam_track_right, :only => [:download, :enqueue, :show_jam_track_right, :mark_active, :download_stem]
|
before_filter :lookup_jam_track_right, :only => [:download, :enqueue, :show_jam_track_right, :mark_active, :download_stem]
|
||||||
before_filter :ip_blacklist, :only => [:download_stem, :download]
|
before_filter :ip_blacklist, :only => [:download_stem, :download]
|
||||||
|
|
@ -23,6 +23,11 @@ class ApiJamTracksController < ApiController
|
||||||
@jam_track = JamTrack.find_by_plan_code!(params[:plan_code])
|
@jam_track = JamTrack.find_by_plan_code!(params[:plan_code])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def public_show
|
||||||
|
@jam_track = JamTrack.find_by!(slug: params[:slug])
|
||||||
|
render "api_jam_tracks/show_for_client", :layout => nil
|
||||||
|
end
|
||||||
|
|
||||||
def index
|
def index
|
||||||
data = JamTrack.index(params, any_user)
|
data = JamTrack.index(params, any_user)
|
||||||
@jam_tracks, @next, @count = data[0], data[1], data[2]
|
@jam_tracks, @next, @count = data[0], data[1], data[2]
|
||||||
|
|
|
||||||
|
|
@ -820,10 +820,6 @@ class ApiMusicSessionsController < ApiController
|
||||||
return render json: { code: "invalid_token", message: "No token found for '#{params[:token]}'" }, status: :forbidden
|
return render json: { code: "invalid_token", message: "No token found for '#{params[:token]}'" }, status: :forbidden
|
||||||
end
|
end
|
||||||
|
|
||||||
if token.expired_at < Time.now.utc
|
|
||||||
return render json: {code: "expired_token", message: "The token has expired" }, status: :forbidden
|
|
||||||
end
|
|
||||||
|
|
||||||
if token.token == Rails.application.config.video_open_room
|
if token.token == Rails.application.config.video_open_room
|
||||||
return render json: { name: token.user.name, user_id: token.user.id }, status: 200
|
return render json: { name: token.user.name, user_id: token.user.id }, status: 200
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,9 @@ class ApiRecurlyController < ApiController
|
||||||
reuse_card: reuse_card_next_time,
|
reuse_card: reuse_card_next_time,
|
||||||
affiliate_referral_id: cookies[:affiliate_visitor],
|
affiliate_referral_id: cookies[:affiliate_visitor],
|
||||||
origin: origin_cookie,
|
origin: origin_cookie,
|
||||||
timezone: current_timezone
|
timezone: current_timezone,
|
||||||
|
facebook_click_id: cookies[:_fbc],
|
||||||
|
facebook_browser_id: cookies[:_fbp]
|
||||||
}
|
}
|
||||||
|
|
||||||
options = User.musician_defaults(request.remote_ip, ApplicationHelper.base_uri(request) + "/confirm", any_user, options)
|
options = User.musician_defaults(request.remote_ip, ApplicationHelper.base_uri(request) + "/confirm", any_user, options)
|
||||||
|
|
|
||||||
|
|
@ -158,7 +158,9 @@ class ApiUsersController < ApiController
|
||||||
affiliate_referral_id: cookies[:affiliate_visitor],
|
affiliate_referral_id: cookies[:affiliate_visitor],
|
||||||
origin: origin_cookie,
|
origin: origin_cookie,
|
||||||
test_drive_package: params[:test_drive_package],
|
test_drive_package: params[:test_drive_package],
|
||||||
timezone: current_timezone
|
timezone: current_timezone,
|
||||||
|
facebook_click_id: cookies[:_fbc],
|
||||||
|
facebook_browser_id: cookies[:_fbp]
|
||||||
}
|
}
|
||||||
|
|
||||||
options = User.musician_defaults(request.remote_ip, ApplicationHelper.base_uri(request) + "/confirm", any_user, options)
|
options = User.musician_defaults(request.remote_ip, ApplicationHelper.base_uri(request) + "/confirm", any_user, options)
|
||||||
|
|
|
||||||
|
|
@ -49,11 +49,22 @@ class ApplicationController < ActionController::Base
|
||||||
|
|
||||||
def origin_cookie
|
def origin_cookie
|
||||||
begin
|
begin
|
||||||
JSON.parse(cookies[:origin]) if cookies[:origin]
|
data = JSON.parse(cookies[:origin]) if cookies[:origin]
|
||||||
rescue
|
rescue
|
||||||
nil
|
data = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Backfill with individual UTM cookies if present
|
||||||
|
# This supports cases where the frontend (jam-ui/web) set specific cookies
|
||||||
|
# or if the JSON cookie is missing/incomplete.
|
||||||
|
%w(utm_source utm_medium utm_campaign utm_term utm_content utm_id).each do |key|
|
||||||
|
if cookies[key].present?
|
||||||
|
data ||= {}
|
||||||
|
data[key] = cookies[key]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
data
|
||||||
end
|
end
|
||||||
|
|
||||||
def track_origin
|
def track_origin
|
||||||
|
|
|
||||||
|
|
@ -635,6 +635,10 @@ class LandingsController < ApplicationController
|
||||||
musician: true,
|
musician: true,
|
||||||
timezone: current_timezone,
|
timezone: current_timezone,
|
||||||
first_name: @first,
|
first_name: @first,
|
||||||
|
origin: origin_cookie,
|
||||||
|
affiliate_referral_id: cookies[:affiliate_visitor],
|
||||||
|
facebook_click_id: cookies[:_fbc],
|
||||||
|
facebook_browser_id: cookies[:_fbp],
|
||||||
instruments: [{:instrument_id => @instrument, :proficiency_level => 1, :priority => 1}])
|
instruments: [{:instrument_id => @instrument, :proficiency_level => 1, :priority => 1}])
|
||||||
if @user.errors.any?
|
if @user.errors.any?
|
||||||
first = @user.errors.first
|
first = @user.errors.first
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,10 @@ class SessionsController < ApplicationController
|
||||||
last_name: auth_hash[:info][:last_name],
|
last_name: auth_hash[:info][:last_name],
|
||||||
email: auth_hash[:info][:email],
|
email: auth_hash[:info][:email],
|
||||||
timezone: current_timezone,
|
timezone: current_timezone,
|
||||||
affiliate_referral_id: cookies[:affiliate_visitor])
|
affiliate_referral_id: cookies[:affiliate_visitor],
|
||||||
|
origin: origin_cookie,
|
||||||
|
facebook_click_id: cookies[:_fbc],
|
||||||
|
facebook_browser_id: cookies[:_fbp])
|
||||||
|
|
||||||
# Users who sign up using oauth are presumed to have valid email adddresses.
|
# Users who sign up using oauth are presumed to have valid email adddresses.
|
||||||
user.confirm_email!
|
user.confirm_email!
|
||||||
|
|
@ -196,7 +199,9 @@ class SessionsController < ApplicationController
|
||||||
location: {:country => nil, :state => nil, :city => nil},
|
location: {:country => nil, :state => nil, :city => nil},
|
||||||
affiliate_referral_id: cookies[:affiliate_visitor],
|
affiliate_referral_id: cookies[:affiliate_visitor],
|
||||||
origin: origin_cookie,
|
origin: origin_cookie,
|
||||||
timezone: current_timezone
|
timezone: current_timezone,
|
||||||
|
facebook_click_id: cookies[:_fbc],
|
||||||
|
facebook_browser_id: cookies[:_fbp]
|
||||||
}
|
}
|
||||||
|
|
||||||
options = User.musician_defaults(request.remote_ip, ApplicationHelper.base_uri(request) + "/confirm", any_user, options)
|
options = User.musician_defaults(request.remote_ip, ApplicationHelper.base_uri(request) + "/confirm", any_user, options)
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,11 @@ class UsersController < ApplicationController
|
||||||
affiliate_partner: @affiliate_partner,
|
affiliate_partner: @affiliate_partner,
|
||||||
timezone: current_timezone,
|
timezone: current_timezone,
|
||||||
origin: origin_cookie,
|
origin: origin_cookie,
|
||||||
desired_plan_code: desired_plan_code )
|
timezone: current_timezone,
|
||||||
|
origin: origin_cookie,
|
||||||
|
desired_plan_code: desired_plan_code,
|
||||||
|
facebook_click_id: cookies[:_fbc],
|
||||||
|
facebook_browser_id: cookies[:_fbp])
|
||||||
|
|
||||||
rend = _render('new')
|
rend = _render('new')
|
||||||
# check for errors
|
# check for errors
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,6 @@ node :jmep do |jam_track|
|
||||||
jam_track.jmep_json ? jam_track.jmep_json : nil
|
jam_track.jmep_json ? jam_track.jmep_json : nil
|
||||||
end
|
end
|
||||||
|
|
||||||
node :can_download do |item|
|
|
||||||
!!item.right_for_user(current_user, ShoppingCart::JAMTRACK_FULL)
|
|
||||||
end
|
|
||||||
|
|
||||||
child(:jam_track_tracks => :tracks) {
|
child(:jam_track_tracks => :tracks) {
|
||||||
|
|
||||||
attributes :id, :part, :instrument, :track_type, :position
|
attributes :id, :part, :instrument, :track_type, :position
|
||||||
|
|
@ -27,22 +23,28 @@ child(:jam_track_tracks => :tracks) {
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
node do |jam_track|
|
if current_user
|
||||||
right = jam_track.right_for_user(current_user)
|
node do |jam_track|
|
||||||
if right
|
right = jam_track.right_for_user(current_user)
|
||||||
{
|
if right
|
||||||
jam_track_right_id: right.id,
|
{
|
||||||
purchased_at: right.created_at.to_i,
|
jam_track_right_id: right.id,
|
||||||
last_mixdown_id: right.last_mixdown_id,
|
purchased_at: right.created_at.to_i,
|
||||||
last_stem_id: right.last_stem_id
|
last_mixdown_id: right.last_mixdown_id,
|
||||||
}
|
last_stem_id: right.last_stem_id
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
node :mixdowns do |jam_track|
|
node :mixdowns do |jam_track|
|
||||||
items = []
|
items = []
|
||||||
jam_track.mixdowns_for_user(current_user).each do |mixdown|
|
jam_track.mixdowns_for_user(current_user).each do |mixdown|
|
||||||
items << partial("api_jam_track_mixdowns/show", :object => mixdown)
|
items << partial("api_jam_track_mixdowns/show", :object => mixdown)
|
||||||
|
end
|
||||||
|
items
|
||||||
|
end
|
||||||
|
|
||||||
|
node :can_download do |item|
|
||||||
|
!!item.right_for_user(current_user, ShoppingCart::JAMTRACK_FULL)
|
||||||
end
|
end
|
||||||
items
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
object @jam_track
|
object @jam_track
|
||||||
|
|
||||||
attributes :id, :name
|
attributes :id, :name, :original_artist
|
||||||
|
|
@ -173,6 +173,12 @@ if defined?(Bundler)
|
||||||
config.google_email = '785931784279-gd0g8on6sc0tuesj7cu763pitaiv2la8@developer.gserviceaccount.com'
|
config.google_email = '785931784279-gd0g8on6sc0tuesj7cu763pitaiv2la8@developer.gserviceaccount.com'
|
||||||
config.google_public_server_key = "AIzaSyCPTPq5PEcl4XWcm7NZ2IGClZlbsiE8JNo"
|
config.google_public_server_key = "AIzaSyCPTPq5PEcl4XWcm7NZ2IGClZlbsiE8JNo"
|
||||||
|
|
||||||
|
# Facebook Conversions API
|
||||||
|
config.facebook_ad_source = Set.new(['facebook', 'instagram', 'meta', 'meta_ads', 'fb', 'ig', 'th', 'threads'])
|
||||||
|
config.facebook_pixel_id = "25663975269956141" # ONLY ONE. NO MULTI ENV
|
||||||
|
config.facebook_access_token = "EABAGQTtcGk4BQewKCztlfImT86ZAPsRAm3KDrYWsOFZAdLtJCa7bVOzdnZCnHZBFZBRMFVoSZCQdIpX01xXDM0ZCFmYrkPuAHdLsNBU61c7MZCZBLCFd3Q0qrxiJkeYr3F8FH3p98d0tGKljplhJ8vWBIKAiBXaUCCYk6urZB5qZAIEPsppWZCHOep91xZCtMVqtbGJQfFAZDZD" # ONLY ONE. NO MULTI ENV
|
||||||
|
config.facebook_conversion_api_tls = true
|
||||||
|
|
||||||
# Use Private API Keys to communicate with Recurly's API v2. See https://docs.recurly.com/api/basics/authentication to learn more.
|
# Use Private API Keys to communicate with Recurly's API v2. See https://docs.recurly.com/api/basics/authentication to learn more.
|
||||||
config.recurly_private_api_key = '55f2fdfa4d014e64a94eaba1e93f39bb'
|
config.recurly_private_api_key = '55f2fdfa4d014e64a94eaba1e93f39bb'
|
||||||
# Use Public Keys to identify your site when using Recurly.js. See https://docs.recurly.com/js/#include to learn more.
|
# Use Public Keys to identify your site when using Recurly.js. See https://docs.recurly.com/js/#include to learn more.
|
||||||
|
|
@ -523,6 +529,7 @@ if defined?(Bundler)
|
||||||
config.signup_survey_url = "https://www.surveymonkey.com/r/WVBKLYL"
|
config.signup_survey_url = "https://www.surveymonkey.com/r/WVBKLYL"
|
||||||
|
|
||||||
config.signup_survey_cutoff_date = "2025-06-10"
|
config.signup_survey_cutoff_date = "2025-06-10"
|
||||||
|
config.profile_complete_reminders_effective_from_date = "2025-06-10"
|
||||||
config.gear_setup_reminders_effective_from_date = "2025-06-10"
|
config.gear_setup_reminders_effective_from_date = "2025-06-10"
|
||||||
config.test_gear_reminders_effective_from_date = "2025-07-24"
|
config.test_gear_reminders_effective_from_date = "2025-07-24"
|
||||||
config.group_session_reminders_effective_from_date = "2025-08-12"
|
config.group_session_reminders_effective_from_date = "2025-08-12"
|
||||||
|
|
|
||||||
|
|
@ -128,6 +128,7 @@ SampleApp::Application.configure do
|
||||||
config.send_user_match_mail_only_to_jamkazam_team = false
|
config.send_user_match_mail_only_to_jamkazam_team = false
|
||||||
config.signup_survey_url = "https://www.surveymonkey.com/r/WVBKLYL"
|
config.signup_survey_url = "https://www.surveymonkey.com/r/WVBKLYL"
|
||||||
config.signup_survey_cutoff_date = "2025-06-10"
|
config.signup_survey_cutoff_date = "2025-06-10"
|
||||||
|
config.profile_complete_reminders_effective_from_date = "2025-06-10"
|
||||||
config.gear_setup_reminders_effective_from_date = "2025-06-10"
|
config.gear_setup_reminders_effective_from_date = "2025-06-10"
|
||||||
config.test_gear_reminders_effective_from_date = "2025-07-24"
|
config.test_gear_reminders_effective_from_date = "2025-07-24"
|
||||||
config.group_session_reminders_effective_from_date = "2025-08-12"
|
config.group_session_reminders_effective_from_date = "2025-08-12"
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ en:
|
||||||
regards: "Best Regards,"
|
regards: "Best Regards,"
|
||||||
signature: "JamKazam Team"
|
signature: "JamKazam Team"
|
||||||
profile_complete_reminder2:
|
profile_complete_reminder2:
|
||||||
subject: "Complete your JamKazam profile"
|
subject: "Take 2 minutes to fill out your JamKazam profile now"
|
||||||
greeting: "Hello"
|
greeting: "Hello"
|
||||||
paragraph1: "We share your profile with JamKazam members who may be a good fit to play music with you during your first week on the platform via an automated email feature. Don’t miss this opportunity to connect. Click the button below to update your profile now, before it’s shared with others!"
|
paragraph1: "We share your profile with JamKazam members who may be a good fit to play music with you during your first week on the platform via an automated email feature. Don’t miss this opportunity to connect. Click the button below to update your profile now, before it’s shared with others!"
|
||||||
update_profile: "Update Profile"
|
update_profile: "Update Profile"
|
||||||
|
|
@ -92,7 +92,7 @@ en:
|
||||||
regards: "Best Regards,"
|
regards: "Best Regards,"
|
||||||
signature: "JamKazam Team"
|
signature: "JamKazam Team"
|
||||||
profile_complete_reminder3:
|
profile_complete_reminder3:
|
||||||
subject: "Complete your JamKazam profile"
|
subject: "Last reminder to update your JamKazam profile"
|
||||||
greeting: "Hello"
|
greeting: "Hello"
|
||||||
paragraph1: "Your profile is your key to connecting with other musicians on JamKazam. It lets others know what instruments you play at what level of proficiency and what kinds of music you like to play. This lets our existing community find and reach out to you to connect and play, and this information also powers our automated matchmaking features that make recommendations to you. If you think you might want to play music live online on JamKazam, please click the button below now to fill out your profile!"
|
paragraph1: "Your profile is your key to connecting with other musicians on JamKazam. It lets others know what instruments you play at what level of proficiency and what kinds of music you like to play. This lets our existing community find and reach out to you to connect and play, and this information also powers our automated matchmaking features that make recommendations to you. If you think you might want to play music live online on JamKazam, please click the button below now to fill out your profile!"
|
||||||
update_profile: "Update Profile"
|
update_profile: "Update Profile"
|
||||||
|
|
|
||||||
|
|
@ -328,6 +328,7 @@ Rails.application.routes.draw do
|
||||||
match '/jamtracks/purchased' => 'api_jam_tracks#purchased', :via => :get, :as => 'api_jam_tracks_purchased'
|
match '/jamtracks/purchased' => 'api_jam_tracks#purchased', :via => :get, :as => 'api_jam_tracks_purchased'
|
||||||
match '/jamtracks/artists' => 'api_jam_tracks#artist_index', :via => :get, :as => 'api_jam_tracks_list_artists'
|
match '/jamtracks/artists' => 'api_jam_tracks#artist_index', :via => :get, :as => 'api_jam_tracks_list_artists'
|
||||||
match '/jamtracks/:id' => 'api_jam_tracks#show', :via => :get, :as => 'api_jam_tracks_show'
|
match '/jamtracks/:id' => 'api_jam_tracks#show', :via => :get, :as => 'api_jam_tracks_show'
|
||||||
|
match '/jamtracks/public/:slug' => 'api_jam_tracks#public_show', :via => :get, :as => 'api_jam_tracks_public_show'
|
||||||
match '/jamtracks/band/:plan_code' => 'api_jam_tracks#show_with_artist_info', :via => :get, :as => 'api_jam_tracks_show_with_artist_info'
|
match '/jamtracks/band/:plan_code' => 'api_jam_tracks#show_with_artist_info', :via => :get, :as => 'api_jam_tracks_show_with_artist_info'
|
||||||
match '/jamtracks' => 'api_jam_tracks#index', :via => :get, :as => 'api_jam_tracks_list'
|
match '/jamtracks' => 'api_jam_tracks#index', :via => :get, :as => 'api_jam_tracks_list'
|
||||||
match '/jamtracks/download/:id' => 'api_jam_tracks#download', :via => :get, :as => 'api_jam_tracks_download'
|
match '/jamtracks/download/:id' => 'api_jam_tracks#download', :via => :get, :as => 'api_jam_tracks_download'
|
||||||
|
|
|
||||||
35911
web/dev_failures.txt
35911
web/dev_failures.txt
File diff suppressed because it is too large
Load Diff
|
|
@ -466,5 +466,158 @@ namespace :jam_tracks do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
task gen_jamtrack_manifest: :environment do |task, arg|
|
||||||
|
|
||||||
|
|
||||||
|
# DOWNLOAD_AUDIO =
|
||||||
|
max = ENV['MAX_JAMTRACKS'].to_i
|
||||||
|
if max == 0
|
||||||
|
max = nil
|
||||||
|
end
|
||||||
|
puts "MAX JAMTRACKS #{max}"
|
||||||
|
|
||||||
|
jam_tracks = JamTrack.includes([:jam_track_tracks,
|
||||||
|
{genres_jam_tracks: :genres},
|
||||||
|
{jam_track_tracks: :instrument},
|
||||||
|
:genres]).where(status: 'Production').order('original_artist, name')
|
||||||
|
private_bucket = Rails.application.config.aws_bucket
|
||||||
|
s3_manager = S3Manager.new(private_bucket, Rails.application.config.aws_access_key_id, Rails.application.config.aws_secret_access_key)
|
||||||
|
|
||||||
|
tmp_dir = Dir.mktmpdir
|
||||||
|
FileUtils.mkdir_p tmp_dir
|
||||||
|
|
||||||
|
puts "tmp_dir=#{tmp_dir}"
|
||||||
|
|
||||||
|
csv_file = File.join(tmp_dir, "manifest.csv")
|
||||||
|
top_folder = File.join(tmp_dir, "audio")
|
||||||
|
FileUtils.mkdir_p(top_folder)
|
||||||
|
|
||||||
|
CSV.open(csv_file, "wb") do |csv|
|
||||||
|
header = ['JamTrackId', 'TrackId', 'Artist', 'Song', 'Instrument', 'Part', 'Type', 'Genre', 'LocalOgg', 'LocalMeta', 's3_path_url_44', 's3_path_url_48', 'AudioExists']
|
||||||
|
csv << header
|
||||||
|
|
||||||
|
jam_tracks.each do |jam_track|
|
||||||
|
song = jam_track.name
|
||||||
|
jam_track.jam_track_tracks.each do |jam_track_track|
|
||||||
|
instrument = jam_track_track.instrument_id
|
||||||
|
part = jam_track_track.part ? jam_track_track.part : ''
|
||||||
|
|
||||||
|
|
||||||
|
# construct the meta file for this track:
|
||||||
|
meta = {}
|
||||||
|
meta[:jam_track_id] = jam_track.id
|
||||||
|
meta[:track_id] = jam_track_track.id
|
||||||
|
meta[:artist] = jam_track.original_artist
|
||||||
|
meta[:song] = jam_track.name
|
||||||
|
meta[:instrument] = instrument
|
||||||
|
meta[:part] = part
|
||||||
|
meta[:type] = jam_track_track.track_type
|
||||||
|
genre = jam_track.genres.first
|
||||||
|
meta[:genre] = genre ? genre.description : ''
|
||||||
|
meta[:s3_path_url_44] = "s3://#{private_bucket}/" + jam_track_track.url_44
|
||||||
|
meta[:s3_path_url_48] = "s3://#{private_bucket}/" + jam_track_track.url_48
|
||||||
|
|
||||||
|
meta_json = File.join(tmp_dir, "meta_#{jam_track.id}_#{jam_track_track.id}.json")
|
||||||
|
File.open(meta_json, "w") do |f|
|
||||||
|
f.write(JSON.pretty_generate(meta))
|
||||||
|
end
|
||||||
|
|
||||||
|
# find the first a-z, 0-9 character and use that
|
||||||
|
first_character = song[0].downcase
|
||||||
|
folder = File.join(top_folder, first_character)
|
||||||
|
if File.exist?(folder) == false
|
||||||
|
FileUtils.mkdir_p(folder)
|
||||||
|
end
|
||||||
|
# folder structure is:
|
||||||
|
# audio/a/song-instrument-part-type.ogg
|
||||||
|
# audio/a/song-instrument-part-type.ogg.meta
|
||||||
|
# run 'part' through a sanitizer to make it filesystem safe
|
||||||
|
part = part.gsub(/[^0-9A-Za-z]/, '_')
|
||||||
|
base_name = File.join(folder, "#{jam_track.id}_#{jam_track_track.id}_#{instrument}_#{part}_#{jam_track_track.track_type}".downcase)
|
||||||
|
ogg_file = "#{base_name}.ogg"
|
||||||
|
|
||||||
|
exists = s3_manager.exists?(jam_track_track.url_48)
|
||||||
|
|
||||||
|
|
||||||
|
row = []
|
||||||
|
row << jam_track.id
|
||||||
|
row << jam_track_track.id
|
||||||
|
row << jam_track.original_artist
|
||||||
|
row << jam_track.name
|
||||||
|
row << instrument
|
||||||
|
row << part
|
||||||
|
row << jam_track_track.track_type
|
||||||
|
genre = jam_track.genres.first
|
||||||
|
row << (genre ? genre.description : '')
|
||||||
|
row << ogg_file.gsub(tmp_dir, '')
|
||||||
|
row << ogg_file.gsub(tmp_dir, '') + ".meta"
|
||||||
|
row << "s3://#{private_bucket}/" + jam_track_track.url_44
|
||||||
|
row << "s3://#{private_bucket}/" + jam_track_track.url_48
|
||||||
|
row << exists
|
||||||
|
|
||||||
|
csv << row
|
||||||
|
|
||||||
|
|
||||||
|
FileUtils.cp(meta_json, "#{ogg_file}.meta")
|
||||||
|
# cleanup meta
|
||||||
|
FileUtils.rm(meta_json)
|
||||||
|
end
|
||||||
|
|
||||||
|
if max > 0
|
||||||
|
max -= 1
|
||||||
|
if max == 0
|
||||||
|
puts "Max of jamtracks reached"
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# dump the 1st 10 lines to stdout for quick verification
|
||||||
|
File.open(csv_file, "r") do|f|
|
||||||
|
10.times do |i|
|
||||||
|
puts f.readline
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
s3_manager.upload('jam_track_manifests/manifest.csv', csv_file, content_type: 'text/csv')
|
||||||
|
|
||||||
|
folder_to_zip = top_folder
|
||||||
|
output_tar_path = Dir.mktmpdir
|
||||||
|
|
||||||
|
output_tar_file = "#{output_tar_path}/archive.tar.gz"
|
||||||
|
|
||||||
|
safe_output = Shellwords.escape(output_tar_file)
|
||||||
|
|
||||||
|
# -c = create
|
||||||
|
# -z = compress with gzip
|
||||||
|
# -f = to a file
|
||||||
|
# -P = (Optional, but useful) Preserve absolute paths.
|
||||||
|
# By default, 'tar' strips the leading '/' for security.
|
||||||
|
# A common pattern is to cd into the parent dir first.
|
||||||
|
|
||||||
|
# Safer way: cd to the directory to get relative paths
|
||||||
|
parent_dir = File.dirname(folder_to_zip)
|
||||||
|
folder_name = File.basename(folder_to_zip)
|
||||||
|
safe_parent = Shellwords.escape(parent_dir)
|
||||||
|
safe_folder_name = Shellwords.escape(folder_name)
|
||||||
|
|
||||||
|
# This command is safer as it creates the archive with relative paths
|
||||||
|
command = "tar -czf #{safe_output} -C #{safe_parent} #{safe_folder_name}"
|
||||||
|
success = system(command)
|
||||||
|
|
||||||
|
if success
|
||||||
|
puts "Successfully created tar.gz file."
|
||||||
|
else
|
||||||
|
puts "Failed to create tar.gz file."
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
s3_manager.upload('jam_track_manifests/all-tracks.tar.gz', safe_output, content_type: 'application/gzip')
|
||||||
|
|
||||||
|
puts "tar.gz output=#{safe_output}"
|
||||||
|
|
||||||
|
puts "tmp_dir=#{tmp_dir}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
namespace :capi do
|
||||||
|
desc "Test Facebook CAPI connection"
|
||||||
|
task :test_connection => :environment do
|
||||||
|
puts "Testing CAPI connection..."
|
||||||
|
pixel_id = APP_CONFIG.facebook_pixel_id
|
||||||
|
access_token = APP_CONFIG.facebook_access_token
|
||||||
|
|
||||||
|
puts "Pixel ID: #{pixel_id}"
|
||||||
|
puts "Access Token: #{access_token.try(:truncate, 10)}"
|
||||||
|
|
||||||
|
if pixel_id.blank? || access_token.blank? || access_token == 'placeholder'
|
||||||
|
puts "WARNING: Configuration missing or placeholder."
|
||||||
|
end
|
||||||
|
|
||||||
|
# Mock user
|
||||||
|
user = User.last
|
||||||
|
user = User.find_by_email('seth@jamkazam.com')
|
||||||
|
if user
|
||||||
|
puts "Sending test event 'StartTrial' for User ID: #{user.id}"
|
||||||
|
#CapiTransmitter.send_event('StartTrial', user, { value: '0.00', currency: 'USD', predicted_ltv: '0.00' }, nil, ENV['TEST_EVENT_CODE'])
|
||||||
|
CapiTransmitter.send_event('AddToCart', user)
|
||||||
|
puts "Check Rails log for output."
|
||||||
|
else
|
||||||
|
puts "No user found in database."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -52,6 +52,8 @@ class UserManager < BaseManager
|
||||||
license_end = options[:license_end]
|
license_end = options[:license_end]
|
||||||
import_source = options[:import_source]
|
import_source = options[:import_source]
|
||||||
desired_plan_code = options[:desired_plan_code]
|
desired_plan_code = options[:desired_plan_code]
|
||||||
|
facebook_click_id = options[:facebook_click_id]
|
||||||
|
facebook_browser_id = options[:facebook_browser_id]
|
||||||
|
|
||||||
recaptcha_failed = false
|
recaptcha_failed = false
|
||||||
unless options[:skip_recaptcha] # allow callers to opt-of recaptcha
|
unless options[:skip_recaptcha] # allow callers to opt-of recaptcha
|
||||||
|
|
@ -114,7 +116,10 @@ class UserManager < BaseManager
|
||||||
license_start: license_start,
|
license_start: license_start,
|
||||||
license_end: license_end,
|
license_end: license_end,
|
||||||
import_source: import_source,
|
import_source: import_source,
|
||||||
desired_plan_code: desired_plan_code)
|
import_source: import_source,
|
||||||
|
desired_plan_code: desired_plan_code,
|
||||||
|
facebook_click_id: facebook_click_id,
|
||||||
|
facebook_browser_id: facebook_browser_id)
|
||||||
user
|
user
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -180,6 +180,22 @@ def web_config
|
||||||
def max_invites_to_receiver_per_day
|
def max_invites_to_receiver_per_day
|
||||||
1000 # a low number only hinders test; the ruby rspec test have a lower value and test this feature
|
1000 # a low number only hinders test; the ruby rspec test have a lower value and test this feature
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def facebook_pixel_id
|
||||||
|
'1234567890'
|
||||||
|
end
|
||||||
|
|
||||||
|
def facebook_ad_source
|
||||||
|
'meta'
|
||||||
|
end
|
||||||
|
|
||||||
|
def facebook_access_token
|
||||||
|
'dummy_token'
|
||||||
|
end
|
||||||
|
|
||||||
|
def facebook_conversion_api_tls
|
||||||
|
true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
klass.new
|
klass.new
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue