Merge branch 'develop' into feature/widget_fixes_1558

This commit is contained in:
Brian Smith 2014-04-28 23:22:08 -04:00
commit 8eedd63d7f
45 changed files with 640 additions and 34 deletions

View File

@ -116,3 +116,7 @@ group :test do
gem 'simplecov-rcov'
end
gem 'pry'
gem 'pry-remote'
gem 'pry-stack_explorer'
gem 'pry-debugger'

View File

@ -0,0 +1,31 @@
ActiveAdmin.register JamRuby::User, :as => 'Referrals' do
menu :label => 'Referrals', :parent => 'Affiliates'
config.batch_actions = false
config.clear_action_items!
config.filters = false
index do
column 'User' do |oo| link_to(oo.name, "http://www.jamkazam.com/client#/profile/#{oo.id}", {:title => oo.name}) end
column 'Email' do |oo| oo.email end
column 'Created' do |oo| oo.created_at end
column 'Partner' do |oo| oo.affiliate_referral.partner_name end
end
controller do
def scoped_collection
rel = end_of_association_chain
.includes([:affiliate_referral])
.order('created_at DESC')
if (ref_id = params[AffiliatePartner::PARAM_REFERRAL]).present?
qq = ['affiliate_referral_id = ?', ref_id]
else
qq = ['affiliate_referral_id IS NOT NULL']
end
@users ||= rel.where(qq)
end
end
end

View File

@ -0,0 +1,49 @@
ActiveAdmin.register JamRuby::AffiliatePartner, :as => 'Affiliates' do
menu :label => 'Partners', :parent => 'Affiliates'
config.sort_order = 'created_at DESC'
config.batch_actions = false
# config.clear_action_items!
config.filters = false
form :partial => 'form'
index do
column 'User' do |oo| link_to(oo.partner_user.name, "http://www.jamkazam.com/client#/profile/#{oo.partner_user.id}", {:title => oo.partner_user.name}) end
column 'Email' do |oo| oo.partner_user.email end
column 'Name' do |oo| oo.partner_name end
column 'Code' do |oo| oo.partner_code end
column 'Referral Count' do |oo| oo.referral_user_count end
# column 'Referrals' do |oo| link_to('View', admin_referrals_path(AffiliatePartner::PARAM_REFERRAL => oo.id)) end
default_actions
end
controller do
def show
redirect_to admin_referrals_path(AffiliatePartner::PARAM_REFERRAL => resource.id)
end
def create
obj = AffiliatePartner.create_with_params(params[:jam_ruby_affiliate_partner])
if obj.errors.present?
set_resource_ivar(obj)
render active_admin_template('new')
else
redirect_to admin_affiliates_path
end
end
def update
obj = resource
vals = params[:jam_ruby_affiliate_partner]
obj.partner_name = vals[:partner_name]
obj.user_email = vals[:user_email] if vals[:user_email].present?
obj.save!
set_resource_ivar(obj)
render active_admin_template('show')
end
end
end

View File

@ -97,12 +97,14 @@ ActiveAdmin.register JamRuby::EmailBatch, :as => 'Batch Emails' do
def create
batch = EmailBatch.create_with_params(params[:jam_ruby_email_batch])
redirect_to admin_batch_email_path(batch.id)
set_resource_ivar(batch)
render active_admin_template('show')
end
def update
resource.update_with_conflict_validation(params[:jam_ruby_email_batch])
redirect_to admin_batch_email_path(resource.id)
set_resource_ivar(resource)
render active_admin_template('show')
end
end

View File

@ -1,7 +1,13 @@
class AdminAuthorization < ActiveAdmin::AuthorizationAdapter
def authorized?(action, subject = nil)
subject.is_a?(EmailBatch) && :update == action ? subject.can_run_batch? : true
if subject.is_a?(EmailBatch) && :update == action
subject.can_run_batch?
elsif subject.is_a?(AffiliatePartner) && :destroy == action
false
else
true
end
end
end

View File

@ -0,0 +1,13 @@
<%= semantic_form_for([:admin, resource], :url => resource.new_record? ? admin_affiliates_path : "/admin/affiliates/#{resource.id}") do |f| %>
<%= f.semantic_errors *f.object.errors.keys %>
<%= f.inputs do %>
<%= f.input(:user_email, :input_html => {:maxlength => 255}) %>
<%= f.input(:partner_name, :input_html => {:maxlength => 128}) %>
<% if resource.new_record? %>
<%= f.input(:partner_code, :input_html => {:maxlength => 128}) %>
<% else %>
<%= f.input(:partner_code, :input_html => {:maxlength => 128, :readonly => 'readonly'}) %>
<% end %>
<% end %>
<%= f.actions %>
<% end %>

View File

@ -40,4 +40,6 @@ JamAdmin::Application.configure do
config.twitter_app_id = 'e7hGc71gmcBgo6Wvdta6Sg'
config.twitter_app_secret = 'PfG1jAUMnyrimPcDooUVQaJrG1IuDjUyGg5KciOo'
config.redis_host = "localhost:6379:1" # go to another db to not cross pollute into dev/production redis dbs
end

View File

@ -143,4 +143,6 @@ emails.sql
email_batch.sql
user_progress_tracking2.sql
bands_did_session.sql
email_change_default_sender.sql
email_change_default_sender.sql
affiliate_partners.sql
chat_messages.sql

View File

@ -0,0 +1,15 @@
CREATE TABLE affiliate_partners (
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
partner_name VARCHAR(128) NOT NULL,
partner_code VARCHAR(128) NOT NULL,
partner_user_id VARCHAR(64) NOT NULL,
user_email VARCHAR(255),
referral_user_count INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX affiliate_partners_code_idx ON affiliate_partners(partner_code);
CREATE INDEX affiliate_partners_user_idx ON affiliate_partners(partner_user_id);
ALTER TABLE users ADD COLUMN affiliate_referral_id VARCHAR(64) REFERENCES affiliate_partners(id);

8
db/up/chat_messages.sql Normal file
View File

@ -0,0 +1,8 @@
CREATE TABLE chat_messages
(
id character varying(64) NOT NULL DEFAULT uuid_generate_v4(),
user_id character varying(64),
music_session_id character varying(64),
messsage TEXT NOT NULL,
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
);

View File

@ -52,6 +52,7 @@ message ClientMessage {
// text message
TEXT_MESSAGE = 236;
CHAT_MESSAGE = 237;
MUSICIAN_SESSION_FRESH = 240;
MUSICIAN_SESSION_STALE = 245;
@ -130,6 +131,7 @@ message ClientMessage {
// text message
optional TextMessage text_message = 236;
optional ChatMessage chat_message = 237;
optional MusicianSessionFresh musician_session_fresh = 240;
optional MusicianSessionStale musician_session_stale = 245;
@ -397,6 +399,14 @@ message TextMessage {
optional bool clipped_msg = 7;
}
message ChatMessage {
optional string sender_name = 1;
optional string sender_id = 2;
optional string msg = 3;
optional string msg_id = 4;
optional string created_at = 5;
}
// route_to: client:
// sent by server to let the rest of the participants know a client has become active again after going stale
message MusicianSessionFresh {

View File

@ -140,6 +140,7 @@ require "jam_ruby/models/email_batch_set"
require "jam_ruby/models/email_error"
require "jam_ruby/app/mailers/async_mailer"
require "jam_ruby/app/mailers/batch_mailer"
require "jam_ruby/models/affiliate_partner"
include Jampb

View File

@ -36,7 +36,7 @@ module JamWebEventMachine
end
def self.run_em(calling_thread)
def self.run_em(calling_thread = nil)
EM.run do
# this is global because we need to check elsewhere if we are currently connected to amqp before signalling success with some APIs, such as 'create session'
@ -54,7 +54,7 @@ module JamWebEventMachine
end
end
calling_thread.wakeup
calling_thread.wakeup if calling_thread
end
end
@ -65,6 +65,7 @@ module JamWebEventMachine
end
def self.run
return if defined?(Rails::Console)
current = Thread.current
Thread.new do
run_em(current)

View File

@ -1,5 +1,6 @@
require 'rest_client'
# more info on Measurement Protocol https://developers.google.com/analytics/devguides/collection/protocol/v1/
class GoogleAnalyticsTracker
attr_accessor :enabled, :tracking_code, :ga_version, :ga_endpoint

View File

@ -580,6 +580,23 @@ module JamRuby
)
end
# creates the session chat message
def chat_message(session_id, sender_name, sender_id, msg, msg_id, created_at)
chat_message = Jampb::ChatMessage.new(
:sender_id => sender_id,
:sender_name => sender_name,
:msg => msg,
:msg_id => msg_id,
:created_at => created_at
)
Jampb::ClientMessage.new(
:type => ClientMessage::Type::CHAT_MESSAGE,
:route_to => SESSION_TARGET_PREFIX + session_id,
:chat_message => chat_message
)
end
# create a musician fresh session message
def musician_session_fresh(session_id, user_id, username, photo_url)
fresh = Jampb::MusicianSessionFresh.new(

View File

@ -0,0 +1,44 @@
class JamRuby::AffiliatePartner < ActiveRecord::Base
belongs_to :partner_user, :class_name => "JamRuby::User", :foreign_key => :partner_user_id
has_many :user_referrals, :class_name => "JamRuby::User", :foreign_key => :affiliate_referral_id
attr_accessible :partner_name, :partner_code, :partner_user_id
PARAM_REFERRAL = :ref
PARAM_COOKIE = :affiliate_ref
PARTNER_CODE_REGEX = /^[#{Regexp.escape('abcdefghijklmnopqrstuvwxyz0123456789-._+,')}]+{2,128}$/i
validates :user_email, format: {with: JamRuby::User::VALID_EMAIL_REGEX}, :if => :user_email
validates :partner_name, presence: true
validates :partner_code, presence: true, format: { with: PARTNER_CODE_REGEX }
validates :partner_user, presence: true
def self.create_with_params(params={})
oo = self.new
oo.partner_name = params[:partner_name].try(:strip)
oo.partner_code = params[:partner_code].try(:strip).try(:downcase)
oo.partner_user = User.where(:email => params[:user_email].try(:strip)).limit(1).first
oo.partner_user_id = oo.partner_user.try(:id)
oo.save
oo
end
def self.coded_id(code=nil)
self.where(:partner_code => code).limit(1).pluck(:id).first if code.present?
end
def self.is_code?(code)
self.where(:partner_code => code).limit(1).pluck(:id).present?
end
def referrals_by_date
by_date = User.where(:affiliate_referral_id => self.id)
.group('DATE(created_at)')
.having("COUNT(*) > 0")
.order('date_created_at DESC')
.count
block_given? ? yield(by_date) : by_date
end
end

View File

@ -0,0 +1,27 @@
module JamRuby
class ChatMessage < ActiveRecord::Base
self.primary_key = 'id'
default_scope order('created_at DESC')
belongs_to :user, :class_name => "JamRuby::User", :foreign_key => "user_id"
belongs_to :session, :class_name => "JamRuby::MusicSession", :foreign_key => "session_id"
validates :message, length: {minimum: 1, maximum: 255}, no_profanity: true
def self.send_chat_msg(music_session, chat_msg, user)
msg = @@message_factory.chat_message(
music_session.id,
user.name,
user.id,
chat_msg.message,
chat_msg.id,
chat_msg.created_at
)
@@mq_router.server_publish_to_session(music_session, msg, sender = {:client_id => chat_msg.user_id})
end
end
end

View File

@ -99,6 +99,10 @@ module JamRuby
# events
has_many :event_sessions, :class_name => "JamRuby::EventSession"
# affiliate_partner
has_one :affiliate_partner, :class_name => "JamRuby::AffiliatePartner", :foreign_key => :partner_user_id
belongs_to :affiliate_referral, :class_name => "JamRuby::AffiliatePartner", :foreign_key => :affiliate_referral_id, :counter_cache => :referral_user_count
# This causes the authenticate method to be generated (among other stuff)
#has_secure_password
@ -735,6 +739,7 @@ module JamRuby
invited_user = options[:invited_user]
fb_signup = options[:fb_signup]
signup_confirm_url = options[:signup_confirm_url]
affiliate_referral_id = options[:affiliate_referral_id]
user = User.new
@ -836,6 +841,10 @@ module JamRuby
if user.errors.any?
raise ActiveRecord::Rollback
else
if user.affiliate_referral = AffiliatePartner.find_by_id(affiliate_referral_id)
user.save
end if affiliate_referral_id.present?
# don't send an signup email if email is already confirmed
if user.email_confirmed
UserMailer.welcome_message(user).deliver

View File

@ -55,6 +55,7 @@ class MQRouter
# sends a message to a client with no checking of permissions (RAW USAGE)
# this method deliberately has no database interactivity/active_record objects
def publish_to_client(client_id, client_msg, sender = {:client_id => ""})
@@log.error "EM not running in publish_to_client" unless EM.reactor_running?
EM.schedule do
sender_client_id = sender[:client_id]
@ -68,6 +69,7 @@ class MQRouter
# sends a message to a session with no checking of permissions (RAW USAGE)
# this method deliberately has no database interactivity/active_record objects
def publish_to_session(music_session_id, client_ids, client_msg, sender = {:client_id => nil})
@@log.error "EM not running in publish_to_session" unless EM.reactor_running?
EM.schedule do
sender_client_id = sender[:client_id]
@ -84,7 +86,7 @@ class MQRouter
# sends a message to a user with no checking of permissions (RAW USAGE)
# this method deliberately has no database interactivity/active_record objects
def publish_to_user(user_id, user_msg)
@@log.warn "EM not running in publish_to_user" unless EM.reactor_running?
@@log.error "EM not running in publish_to_user" unless EM.reactor_running?
EM.schedule do
@@log.debug "publishing to user:#{user_id} from server"
@ -96,6 +98,8 @@ class MQRouter
# sends a message to a list of friends with no checking of permissions (RAW USAGE)
# this method deliberately has no database interactivity/active_record objects
def publish_to_friends(friend_ids, user_msg, from_user_id)
@@log.error "EM not running in publish_to_friends" unless EM.reactor_running?
EM.schedule do
friend_ids.each do |friend_id|
@@log.debug "publishing to friend:#{friend_id} from user/band #{from_user_id}"

View File

@ -1,5 +1,6 @@
require 'resque'
# more info on Measurement Protocol https://developers.google.com/analytics/devguides/collection/protocol/v1/
module JamRuby
class GoogleAnalyticsEvent

View File

@ -0,0 +1,75 @@
require 'spec_helper'
describe AffiliatePartner do
let!(:user) { FactoryGirl.create(:user) }
let!(:partner) {
AffiliatePartner.create_with_params({:partner_name => Faker::Company.name,
:partner_code => Faker::Lorem.word,
:user_email => user.email})
}
it 'validates required fields' do
pending
expect(partner.referral_user_count).to eq(0)
expect(partner.partner_user).to eq(user)
user.reload
expect(user.affiliate_partner).to eq(partner)
oo = AffiliatePartner.create_with_params({:partner_name => Faker::Company.name,
:partner_code => 'a',
:user_email => user.email})
expect(oo.errors.messages[:partner_code][0]).to eq('is invalid')
oo = AffiliatePartner.create_with_params({:partner_name => Faker::Company.name,
:partner_code => 'foo bar',
:user_email => user.email})
expect(oo.errors.messages[:partner_code][0]).to eq('is invalid')
oo = AffiliatePartner.create_with_params({:partner_name => '',
:partner_code => Faker::Lorem.word,
:user_email => user.email})
expect(oo.errors.messages[:partner_name][0]).to eq("can't be blank")
oo = AffiliatePartner.create_with_params({:partner_name => '',
:partner_code => Faker::Lorem.word,
:user_email => Faker::Internet.email})
expect(oo.errors.messages[:partner_user][0]).to eq("can't be blank")
code = Faker::Lorem.word.upcase
oo = AffiliatePartner.create_with_params({:partner_name => Faker::Company.name,
:partner_code => " #{code} ",
:user_email => user.email})
expect(oo.partner_code).to eq(code.downcase)
end
it 'has user referrals' do
pending
expect(AffiliatePartner.coded_id(partner.partner_code)).to eq(partner.id)
expect(partner.referral_user_count).to eq(0)
uu = FactoryGirl.create(:user)
uu.affiliate_referral = partner
uu.save
partner.reload
expect(uu.affiliate_referral).to eq(partner)
expect(partner.referral_user_count).to eq(1)
expect(partner.user_referrals[0]).to eq(uu)
end
it 'groups referrals properly' do
FactoryGirl.create(:user, :created_at => Time.now - 7.days, :affiliate_referral_id => partner.id)
FactoryGirl.create(:user, :created_at => Time.now - 7.days, :affiliate_referral_id => partner.id)
FactoryGirl.create(:user, :created_at => Time.now - 6.days, :affiliate_referral_id => partner.id)
FactoryGirl.create(:user, :created_at => Time.now - 6.days, :affiliate_referral_id => partner.id)
FactoryGirl.create(:user, :created_at => Time.now - 3.days, :affiliate_referral_id => partner.id)
FactoryGirl.create(:user, :created_at => Time.now - 2.days, :affiliate_referral_id => partner.id)
partner.reload
expect(partner.referral_user_count).to eq(6)
by_date = partner.referrals_by_date
expect(by_date.count).to eq(4)
keys = by_date.keys
expect(Date.parse(keys.first)).to eq(Date.parse((Time.now - 2.days).to_s))
expect(by_date[keys.first]).to eq(1)
expect(Date.parse(keys.last)).to eq(Date.parse((Time.now - 7.days).to_s))
expect(by_date[keys.last]).to eq(2)
end
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 B

View File

@ -51,6 +51,7 @@
// text message
TEXT_MESSAGE : "TEXT_MESSAGE",
CHAT_MESSAGE : "CHAT_MESSAGE",
// broadcast notifications
SOURCE_UP_REQUESTED : "SOURCE_UP_REQUESTED",

View File

@ -0,0 +1,65 @@
(function(context,$) {
"use strict";
context.JK = context.JK || {};
context.JK.AffiliateReportScreen = function(app) {
var logger = context.JK.logger;
var rest = context.JK.Rest();
var user = {};
function beforeShow(data) {
}
function afterShow(data) {
renderAffiliateReport();
}
function populateAffiliateReport(report) {
console.log(report);
var by_date = '';
var ii=0, dates_len = report['by_date'].length;
for (var ii=0; ii < dates_len; ii += 1) {
var dd = report['by_date'][ii];
by_date += '<div style="float:left; margin-left:50px">'+dd[0]+'</div>';
by_date += '<div style="float:left; margin-left:20px;">'+dd[1].toString()+'</div>';
by_date += '<br />';
}
var template = context.JK.fillTemplate($('#template-account-affiliate').html(), {
total_count: report['total_count'],
by_date: by_date
});
$('#account-affiliate-content-scroller').html(template);
}
/****************** MAIN PORTION OF SCREEN *****************/
// events for main screen
function events() {
}
function renderAffiliateReport() {
$.ajax({
type: "GET",
dataType: "json",
url: "/api/users/" + context.JK.currentUserId + "/affiliate",
processData: false
}).done(populateAffiliateReport)
.error(app.ajaxError);
}
function initialize() {
var screenBindings = {
'beforeShow': beforeShow,
'afterShow': afterShow
};
app.bindScreen('account/affiliate', screenBindings);
events();
}
this.initialize = initialize;
this.beforeShow = beforeShow;
this.afterShow = afterShow;
return this;
};
})(window,jQuery);

View File

View File

@ -944,6 +944,24 @@
});
}
function createChatMessage(session_id, options) {
return $.ajax({
type: "POST",
url: '/api/sessions/' + session_id + '/chat?' + $.param(options),
dataType: "json",
contentType: 'application/json'
});
}
function getChatMessages(session_id, options) {
return $.ajax({
type: "GET",
url: '/api/sessions/' + session_id + '/chats' + $.param(options),
dataType: "json",
contentType: 'application/json'
});
}
function initialize() {
return self;
}
@ -1026,6 +1044,8 @@
this.createFbInviteUrl = createFbInviteUrl;
this.createTextMessage = createTextMessage;
this.getNotifications = getNotifications;
this.createChatMessage = createChatMessage;
this.getChatMessages = getChatMessages;
return this;
};

View File

@ -36,8 +36,8 @@
var localResults = context.jamClient.GetLocalRecordingState({recordings: [recording]});
if (localResults['error']) {
logger.error("unable to open recording due to error: %o", localResults);
app.notify({
title: "Unable to Open Recording for Playback",
text: localResults['error'],
@ -47,6 +47,7 @@
else {
var localResult = localResults.recordings[0];
if (localResult.aggregate_state == 'MISSING') {
logger.error("unable to open recording due to all missing tracks: %o", localResults);
app.notify({
title: "Unable to Open Recording for Playback",
text: "All tracks associated with the recording are missing",
@ -54,6 +55,7 @@
});
}
else if (localResult.aggregate_state == 'PARTIALLY_MISSING') {
logger.error("unable to open recording due to some missing tracks: %o", localResults);
app.notify({
title: "Unable to Open Recording for Playback",
text: "Some tracks associated with the recording are missing",

View File

@ -21,6 +21,7 @@
var currentTrackChanges = 0;
// we track all the clientIDs of all the participants ever seen by this session, so that we can reliably convert a clientId from the backend into a username/avatar
var participantsEverSeen = {};
var $self = $(this);
function id() {
return currentSession ? currentSession.id : null;
@ -90,6 +91,7 @@
server.registerMessageCallback(context.JK.MessageType.SESSION_JOIN, trackChanges);
server.registerMessageCallback(context.JK.MessageType.SESSION_DEPART, trackChanges);
server.registerMessageCallback(context.JK.MessageType.TRACKS_CHANGED, trackChanges);
$(document).trigger('jamkazam.session_started', {session: {id: sessionId}});
})
.fail(function() {
currentSessionId = null;
@ -126,6 +128,7 @@
context.jamClient.SessionSetAlertCallback("");
context.jamClient.SessionSetConnectionStatusRefreshRate(0);
updateCurrentSession(null);
$(document).trigger('jamkazam.session_stopped', {session: {id: currentSessionId}});
currentSessionId = null;
}
/**

View File

@ -10,6 +10,7 @@
var invitationDialog = null;
var textMessageDialog = null;
var notificationPanel = null;
var chatPanel = null;
var me = null;
function initializeSearchPanel() {
@ -95,6 +96,8 @@
}
function initializeChatPanel() {
// chatPanel = new context.JK.ChatPanel(app);
// chatPanel.initialize(me, textMessageDialog);
}
function search(query) {

View File

@ -22,5 +22,9 @@
.buttons {
margin:0 20px 20px 0;
}
.close-btn {
display:none;
}
}

View File

@ -429,7 +429,7 @@ ul.shortcuts {
padding:2px;
}
.account-home, .band-setup, .audio, .get-help, .download-app, .community-forum, .invite-friends {
.account-home, .band-setup, .account-menu-group, .get-help, .download-app, .community-forum, .invite-friends {
border-bottom:1px;
border-style:solid;
border-color:#ED3618;

View File

@ -0,0 +1,35 @@
class ApiChatsController < ApiController
before_filter :api_signed_in_user, :check_session
respond_to :json
def create
@chat_msg = ChatMessage.new
@chat_msg.user_id = current_user.id
@chat_msg.session_id = params[:music_session]
@chat_msg.message = params[:msg]
if @chat_msg.save
ChatMessage.send_chat_msg @music_session, @chat_msg, current_user
end
respond_with_model(@chat_msg)
end
def index
@chat_msgs = ChatMessage.find_by_session_id(params[:music_session])
.paginate(page: params[:page], per_page: pararms[:per_page] || 20)
end
def check_session
@music_session = MusicSession.find(params[:music_session])
if @music_session.nil?
raise ArgumentError, 'specified session not found'
end
unless @music_session.access? current_user
raise PermissionError, 'not allowed to join the specified session'
end
end
end

View File

@ -11,7 +11,8 @@ class ApiUsersController < ApiController
:notification_index, :notification_destroy, # notifications
:band_invitation_index, :band_invitation_show, :band_invitation_update, # band invitations
:set_password, :begin_update_email, :update_avatar, :delete_avatar, :generate_filepicker_policy,
:share_session, :share_recording]
:share_session, :share_recording,
:affiliate_report]
respond_to :json
@ -605,6 +606,27 @@ class ApiUsersController < ApiController
end
end
def affiliate_report
begin
affiliate = User
.where(:id => params[:id])
.includes(:affiliate_partner)
.limit(1)
.first
.affiliate_partner
referrals_by_date = affiliate.referrals_by_date do |by_date|
by_date.inject([]) { |rr, key| rr << key }
end
result = {
:total_count => affiliate.referral_user_count,
:by_date => referrals_by_date
}
render json: result.to_json, status: 200
rescue
render :json => { :message => $!.to_s }, :status => 400
end
end
def add_play
if params[:id].blank?
render :json => { :message => "Playable ID is required" }, :status => 400

View File

@ -4,10 +4,22 @@ class ApplicationController < ActionController::Base
include ApplicationHelper
include SessionsHelper
# inject username/email into bugsnag data
before_bugsnag_notify :add_user_info_to_bugsnag
before_filter do
if params[AffiliatePartner::PARAM_REFERRAL].present? && current_user.nil?
if cookies[AffiliatePartner::PARAM_COOKIE].blank?
code = params[AffiliatePartner::PARAM_REFERRAL].downcase
cookies[AffiliatePartner::PARAM_COOKIE] = code if AffiliatePartner.is_code?(code)
end
end
end
def affiliate_code
cookies[AffiliatePartner::PARAM_COOKIE]
end
private
def add_user_info_to_bugsnag(notif)
# Add some app-specific data which will be displayed on a custom

View File

@ -149,19 +149,20 @@ class UsersController < ApplicationController
musician = params[:jam_ruby_user][:musician]
@user = UserManager.new.signup(remote_ip: request.remote_ip,
first_name: params[:jam_ruby_user][:first_name],
last_name: params[:jam_ruby_user][:last_name],
email: params[:jam_ruby_user][:email],
password: params[:jam_ruby_user][:password],
password_confirmation: params[:jam_ruby_user][:password_confirmation],
terms_of_service: terms_of_service,
instruments: instruments,
birth_date: birth_date,
location: location,
musician: musician,
invited_user: @invited_user,
fb_signup: @fb_signup,
signup_confirm_url: ApplicationHelper.base_uri(request) + "/confirm")
first_name: params[:jam_ruby_user][:first_name],
last_name: params[:jam_ruby_user][:last_name],
email: params[:jam_ruby_user][:email],
password: params[:jam_ruby_user][:password],
password_confirmation: params[:jam_ruby_user][:password_confirmation],
terms_of_service: terms_of_service,
instruments: instruments,
birth_date: birth_date,
location: location,
musician: musician,
invited_user: @invited_user,
fb_signup: @fb_signup,
signup_confirm_url: ApplicationHelper.base_uri(request) + "/confirm",
affiliate_referral_id: AffiliatePartner.coded_id(self.affiliate_code))
# check for errors
if @user.errors.any?

View File

@ -0,0 +1,41 @@
<!-- Account Summary Dialog -->
<div layout="screen" layout-id="account/affiliate" class="screen secondary" id="account-affiliate">
<!-- header -->
<div class="content-head">
<!-- icon -->
<div class="content-icon">
<%= image_tag "content/icon_dollar.png", {:width => 24, :height => 24} %>
</div>
<!-- section head text -->
<h1>affiliate report</h1>
<%= render "screen_navigation" %>
</div>
<!-- end header -->
<!-- profile scrolling area -->
<div class="content-body">
<div id="account-affiliate-content-scroller" class="content-body-scroller">
</div>
</div>
<!-- end content scrolling area -->
</div>
<script type="text/template" id="template-account-affiliate">
<!-- content wrapper -->
<div class="content-wrapper account-affiliate">
<br />
<div class="account-left">
<div style='float:right; margin-right:20px;'><h2>Total Count:&nbsp;&nbsp;{total_count}</h2></div>
<br />
<div style="overflow:scroll; margin-left: 50px; margin-top: 20px">
<div style="float:left;">Registration Date</div><div style="float:left; margin-left:20px">Number of Users</div>
<br />
{by_date}
</div>
</div>
<br clear="all" />
</div>
<!-- end content wrapper -->
</script>

View File

@ -78,7 +78,7 @@
<td id="tdBandBiography" valign="top" colspan="2">
<div class="field">
<label for="band-biography">Description / Bio:</label>
<textarea id="band-biography" class="band-setup-bio w90" maxlength="4000"></textarea>
<textarea id="band-biography" class="band-setup-bio w90"></textarea>
</div>
</td>
</tr>

View File

@ -38,6 +38,7 @@
<%= render "testBridge" %>
<%= render "account" %>
<%= render "account_identity" %>
<%= render "affiliate_report" %>
<%= render "account_profile" %>
<%= render "friendSelector" %>
<%= render "account_profile_avatar" %>
@ -184,6 +185,9 @@
var accountIdentityScreen = new JK.AccountIdentityScreen(JK.app);
accountIdentityScreen.initialize();
var affiliateReportScreen = new JK.AffiliateReportScreen(JK.app);
affiliateReportScreen.initialize();
var accountProfileScreen = new JK.AccountProfileScreen(JK.app);
accountProfileScreen.initialize();

View File

@ -20,7 +20,13 @@
<!--<li class="subscriptions"><%= link_to "Subscriptions", '/client#/account/subscriptions' %></li> -->
<!-- <li class="payments"><%= link_to "Payments", '/client#/account/payments' %></li> -->
<% if current_user && current_user.musician? %>
<li class="audio"><%= link_to "Audio Gear", '/client#/account/audio' %></li>
<% class_val = current_user.affiliate_partner.present? ? 'audio' : 'audio account-menu-group' %>
<li class="<%= class_val%>"><%= link_to "Audio Gear", '/client#/account/audio' %></li>
<% end %>
<% if current_user && current_user.affiliate_partner.present? %>
<li class="affiliate account-menu-group"><%= link_to "Affiliate Report", '/client#/account/affiliate' %></li>
<% end %>
<% if current_user && current_user.musician? %>
<li class="band-setup"><%= link_to "Band Setup", '/client#/band/setup/new' %></li>
<% end %>
<li class="invite-friends"><span class='menuheader'><span class="arrow-right"></span><%= link_to "Invite Friends", '#' %></span>

View File

@ -51,6 +51,8 @@ SampleApp::Application.configure do
# through jam-admin, then jam-web can point users at it. I think 99% of devs won't even see or care about this config, and 0% of users
config.jam_admin_root_url = 'http://localhost:3333'
config.redis_host = "localhost:6379:1" # go to another db to not cross pollute into dev/production redis dbs
config.storage_type = :file
config.aws_bucket = 'jamkazam-testing'

View File

@ -259,6 +259,10 @@ SampleApp::Application.routes.draw do
match '/users/:id/share/session/:provider' => 'api_users#share_session', :via => :get
match '/users/:id/share/recording/:provider' => 'api_users#share_recording', :via => :get
# session chat
match '/sessions/:id/chat' => 'api_chats#create', :via => :post
match '/sessions/:id/chats' => 'api_chats#index', :via => :get
# user recordings
# match '/users/:id/recordings' => 'api_users#recording_index', :via => :get
# match '/users/:id/recordings/:recording_id' => 'api_users#recording_show', :via => :get, :as => 'api_recording_detail'
@ -267,6 +271,7 @@ SampleApp::Application.routes.draw do
# match '/users/:id/recordings/:recording_id' => 'api_users#recording_destroy', :via => :delete
match '/users/:id/plays' => 'api_users#add_play', :via => :post, :as => 'api_users_add_play'
match '/users/:id/affiliate' => 'api_users#affiliate_report', :via => :get, :as => 'api_users_affiliate'
# bands
match '/bands' => 'api_bands#index', :via => :get

View File

@ -17,11 +17,11 @@ worker_processes 4
# as root unless it's from system init scripts.
# If running the master process as root and the workers as an unprivileged
# user, do this to switch euid/egid in the workers (also chowns logs):
user "jam-web", "jam-web"
#user "jam-web", "jam-web"
# Help ensure your application will always spawn in the symlinked
# "current" directory that Capistrano sets up.
working_directory "/var/lib/jam-web" # available in 0.94.0+
#working_directory "/var/lib/jam-web" # available in 0.94.0+
# listen on both a Unix domain socket and a TCP port,
# we use a shorter backlog for quicker failover when busy
@ -32,13 +32,13 @@ listen 3100, :tcp_nopush => true
timeout 30
# feel free to point this anywhere accessible on the filesystem
pid "/var/run/jam-web/jam-web.pid"
#pid "/var/run/jam-web/jam-web.pid"
# By default, the Unicorn logger will write to stderr.
# Additionally, ome applications/frameworks log to stderr or stdout,
# so prevent them from going to /dev/null when daemonized here:
stderr_path "/var/lib/jam-web/log/unicorn.stderr.log"
stdout_path "/var/lib/jam-web/log/unicorn.stdout.log"
#stderr_path "/var/lib/jam-web/log/unicorn.stderr.log"
#stdout_path "/var/lib/jam-web/log/unicorn.stdout.log"
# combine Ruby 2.0.0dev or REE with "preload_app true" for memory savings
# http://rubyenterpriseedition.com/faq.html#adapt_apps_for_cow
@ -95,7 +95,11 @@ after_fork do |server, worker|
ActiveRecord::Base.establish_connection
Thread.new do
JamWebEventMachine.run_em
begin
JamWebEventMachine.run_em
rescue Exception => e
puts "unable to start eventmachine in after_fork!!: #{e}"
end
end
# if preload_app is true, then you may also want to check and
# restart any other shared sockets/descriptors such as Memcached,

View File

@ -26,6 +26,7 @@ class UserManager < BaseManager
invited_user = options[:invited_user]
fb_signup = options[:fb_signup]
signup_confirm_url = options[:signup_confirm_url]
affiliate_referral_id = options[:affiliate_referral_id]
@user = User.new
@ -60,7 +61,8 @@ class UserManager < BaseManager
photo_url: photo_url,
invited_user: invited_user,
fb_signup: fb_signup,
signup_confirm_url: signup_confirm_url)
signup_confirm_url: signup_confirm_url,
affiliate_referral_id: affiliate_referral_id)
return @user
#end

View File

@ -11,6 +11,13 @@ describe UserManager do
end
describe "signup" do
let!(:user) { FactoryGirl.create(:user) }
let!(:partner) {
AffiliatePartner.create_with_params({:partner_name => Faker::Company.name,
:partner_code => Faker::Lorem.words(1)[0],
:user_email => user.email})
}
it "signup successfully" do
MaxMindIsp.delete_all # prove that city/state/country will remain nil if no maxmind data
MaxMindGeo.delete_all
@ -24,7 +31,8 @@ describe UserManager do
terms_of_service: true,
instruments: @instruments,
musician:true,
signup_confirm_url: "http://localhost:3000/confirm" )
signup_confirm_url: "http://localhost:3000/confirm",
affiliate_referral_id: AffiliatePartner.coded_id(partner.partner_code))
@user.errors.any?.should be_false
@user.first_name.should == "bob"
@ -38,6 +46,9 @@ describe UserManager do
@user.subscribe_email.should be_true
@user.signup_token.should_not be_nil
@user.reload
expect(@user.affiliate_referral).to eq(partner)
UserMailer.deliveries.length.should == 1
end

View File

@ -0,0 +1,41 @@
require 'spec_helper'
describe "Affiliate Reports", :type => :api do
include Rack::Test::Methods
let!(:user) { FactoryGirl.create(:user) }
let!(:partner) {
AffiliatePartner.create_with_params({:partner_name => Faker::Company.name,
:partner_code => Faker::Lorem.words[0],
:user_email => user.email})
}
it "valid score" do
FactoryGirl.create(:user, :created_at => Time.now - 5.days, :affiliate_referral_id => partner.id)
FactoryGirl.create(:user, :created_at => Time.now - 6.days, :affiliate_referral_id => partner.id)
FactoryGirl.create(:user, :created_at => Time.now - 6.days, :affiliate_referral_id => partner.id)
FactoryGirl.create(:user, :created_at => Time.now - 7.days, :affiliate_referral_id => partner.id)
FactoryGirl.create(:user, :created_at => Time.now - 7.days, :affiliate_referral_id => partner.id)
FactoryGirl.create(:user, :created_at => Time.now - 7.days, :affiliate_referral_id => partner.id)
FactoryGirl.create(:user, :created_at => Time.now - 7.days, :affiliate_referral_id => partner.id)
post('/api/auth_session.json',
{ :email => user.email, :password => user.password }.to_json,
"CONTENT_TYPE" => 'application/json')
last_response.status.should == 200
expect(JSON.parse(last_response.body)).to eq({ "success" => true })
get "/api/users/#{user.id}/affiliate"
expect(last_response.status).to eq(200)
json = JSON.parse(last_response.body)
expect(json['total_count']).to eq(7)
by_date = json['by_date']
expect(by_date.count).to eq(3)
expect(by_date.first.last).to eq(1)
expect(by_date.last.last).to eq(4)
end
end