referrer 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ module Referrer
2
+ class ApplicationController < ActionController::Base
3
+ protect_from_forgery with: :exception
4
+ end
5
+ end
@@ -0,0 +1,22 @@
1
+ require_dependency 'referrer/application_controller'
2
+
3
+ module Referrer
4
+ class SessionsController < ApplicationController
5
+ def create
6
+ user = Referrer::User.where(id: session_params[:user_id], token: session_params[:user_token]).first
7
+ if user
8
+ @session = user.sessions.active_at(Time.now).first
9
+ @session = user.sessions.create! unless @session
10
+ render json: {id: @session.id, active_seconds: @session.active_seconds}
11
+ else
12
+ render json: {errors: ['User token is incorrect']}, status: 401
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def session_params
19
+ params.require(:session).permit(:user_id, :user_token)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,31 @@
1
+ require_dependency 'referrer/application_controller'
2
+
3
+ module Referrer
4
+ class SourcesController < ApplicationController
5
+ def mass_create
6
+ user = Referrer::User.where(id: mass_source_params[:user_id], token: mass_source_params[:user_token]).first
7
+ if user.present?
8
+ sessions = user.sessions
9
+ @sources = JSON.parse(mass_source_params[:values]).inject([]) do |r, pack|
10
+ session ||= sessions.detect{|s| s.id == pack['session_id'].to_i} ||
11
+ sessions.detect{|s| s.id == mass_source_params[:current_session_id].to_i}
12
+ if session.blank? || session.sources.exists?(client_duplicate_id: pack['client_duplicate_id'])
13
+ r
14
+ else
15
+ r << session.sources.create!(entry_point: pack['entry_point'], referrer: pack['referrer'],
16
+ client_duplicate_id: pack['client_duplicate_id'])
17
+ end
18
+ end
19
+ render json: {ids: @sources.map(&:id)}
20
+ else
21
+ render json: {errors: ['User token is incorrect']}, status: 401
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def mass_source_params
28
+ params.require(:sources).permit(:user_id, :current_session_id, :user_token, :values)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,10 @@
1
+ require_dependency 'referrer/application_controller'
2
+
3
+ module Referrer
4
+ class UsersController < ApplicationController
5
+ def create
6
+ @user = Referrer::User.create!
7
+ render json: {id: @user.id, token: @user.token}
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,4 @@
1
+ module Referrer
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,27 @@
1
+ module Referrer
2
+ class Session < ActiveRecord::Base
3
+ belongs_to :user
4
+ has_many :sources
5
+
6
+ validates_presence_of :user, :active_until
7
+
8
+ before_validation :set_active_period
9
+
10
+ scope :active_at, -> (time) { where('active_from <= :time AND active_until >= :time', {time: time}) }
11
+
12
+ def active_seconds
13
+ (active_until - Time.now).to_i
14
+ end
15
+
16
+ def source_at(time)
17
+ sources.priority.where('created_at <= ?', time).last
18
+ end
19
+
20
+ private
21
+
22
+ def set_active_period
23
+ self.active_from = Time.now unless active_from
24
+ self.active_until = Time.now + Referrer.session_duration unless active_until
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,46 @@
1
+ module Referrer
2
+ class Source < ActiveRecord::Base
3
+ belongs_to :session
4
+
5
+ validates_presence_of :entry_point, :utm_source, :utm_campaign, :utm_medium, :utm_content, :utm_term, :session,
6
+ :kind, :client_duplicate_id
7
+
8
+ before_validation :fill_markup_fields, on: :create
9
+ before_create :set_priority
10
+
11
+ scope :priority, -> {where(priority: true)}
12
+
13
+ class << self
14
+ def markup_generator
15
+ @markup_generator ||= begin
16
+ mg = MarkupGenerator.new
17
+ Referrer.markup_generator_settings.each do |k, v|
18
+ mg.send(:"#{k}=", v)
19
+ end
20
+ mg
21
+ end
22
+ end
23
+ end
24
+
25
+ def to_markup
26
+ attributes.slice(*%w{utm_source utm_campaign utm_medium utm_content utm_term kind})
27
+ end
28
+
29
+ private
30
+
31
+ def fill_markup_fields
32
+ markup = self.class.markup_generator.generate(referrer, entry_point)
33
+ %i{utm_source utm_campaign utm_medium utm_content utm_term kind}.each do |column_name|
34
+ self.send(:"#{column_name}=", markup[column_name])
35
+ end
36
+ end
37
+
38
+ def set_priority
39
+ previous_priority_source = session.sources.where(priority: true).last
40
+ if previous_priority_source.blank? ||
41
+ Referrer.sources_overwriting_schema[kind.to_sym].include?(previous_priority_source.kind)
42
+ self.priority = true
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,17 @@
1
+ module Referrer
2
+ class SourcesTrackedObject < ActiveRecord::Base
3
+ belongs_to :trackable, polymorphic: true
4
+ belongs_to :user
5
+ belongs_to :source
6
+
7
+ validates_presence_of :trackable, :user, :linked_at
8
+ before_validation :set_fields, on: :create
9
+
10
+ private
11
+
12
+ def set_fields
13
+ self.linked_at = Time.now unless linked_at
14
+ self.source = user.try(:source_at, linked_at) if linked_at
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,32 @@
1
+ module Referrer
2
+ class User < ActiveRecord::Base
3
+ has_many :sessions
4
+ has_many :users_main_app_users
5
+
6
+ validates :token, presence: true
7
+
8
+ before_validation :generate_token, on: :create
9
+
10
+ def link_with(obj)
11
+ users_main_app_users.create(main_app_user: obj)
12
+ end
13
+
14
+ def linked_with?(obj)
15
+ users_main_app_users.where(main_app_user: obj).present?
16
+ end
17
+
18
+ def linked_objects
19
+ users_main_app_users.includes(:main_app_user).map{|relation| relation.main_app_user}
20
+ end
21
+
22
+ def source_at(time)
23
+ sessions.active_at(time).first.try(:source_at, time)
24
+ end
25
+
26
+ private
27
+
28
+ def generate_token
29
+ self.token = SecureRandom.hex(5)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,6 @@
1
+ module Referrer
2
+ class UsersMainAppUser < ActiveRecord::Base
3
+ belongs_to :main_app_user, polymorphic: true
4
+ belongs_to :user
5
+ end
6
+ end
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Referrer</title>
5
+ <%= stylesheet_link_tag "referrer/application", media: "all" %>
6
+ <%= javascript_include_tag "referrer/application" %>
7
+ <%= csrf_meta_tags %>
8
+ </head>
9
+ <body>
10
+
11
+ <%= yield %>
12
+
13
+ </body>
14
+ </html>
data/config/routes.rb ADDED
@@ -0,0 +1,9 @@
1
+ Referrer::Engine.routes.draw do
2
+ resources :users, only: :create
3
+ resources :sessions, only: :create
4
+ resources :sources, only: [] do
5
+ collection do
6
+ post :mass_create
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,58 @@
1
+ class CreateReferrerInitStructure < ActiveRecord::Migration
2
+ def change
3
+ create_table :referrer_users do |t|
4
+ t.string :token
5
+
6
+ t.timestamps null: false
7
+ end
8
+
9
+ create_table :referrer_users_main_app_users do |t|
10
+ t.integer :user_id
11
+ t.integer :main_app_user_id
12
+ t.string :main_app_user_type
13
+
14
+ t.timestamps null: false
15
+ end
16
+ add_index :referrer_users_main_app_users, [:main_app_user_type, :main_app_user_id], name: 'referrer_users_main_app_users_mau'
17
+ add_index :referrer_users_main_app_users, :user_id
18
+
19
+ create_table :referrer_sessions do |t|
20
+ t.integer :user_id
21
+ t.datetime :active_from
22
+ t.datetime :active_until
23
+
24
+ t.timestamps null: false
25
+ end
26
+ add_index :referrer_sessions, :user_id
27
+
28
+ create_table :referrer_sources do |t|
29
+ t.integer :session_id
30
+ t.string :referrer
31
+ t.string :entry_point
32
+ t.integer :client_duplicate_id
33
+ t.string :utm_source
34
+ t.string :utm_campaign
35
+ t.string :utm_medium
36
+ t.string :utm_content
37
+ t.string :utm_term
38
+ t.string :kind
39
+ t.boolean :priority, default: false
40
+
41
+ t.timestamps null: false
42
+ end
43
+ add_index :referrer_sources, :session_id
44
+
45
+ create_table :referrer_sources_tracked_objects do |t|
46
+ t.integer :user_id
47
+ t.integer :source_id
48
+ t.datetime :linked_at
49
+ t.integer :trackable_id
50
+ t.string :trackable_type
51
+
52
+ t.timestamps null: false
53
+ end
54
+ add_index :referrer_sources_tracked_objects, [:trackable_type, :trackable_id], name: 'referrer_sources_tracked_objects_ta'
55
+ add_index :referrer_sources_tracked_objects, :user_id
56
+ add_index :referrer_sources_tracked_objects, :source_id
57
+ end
58
+ end
@@ -0,0 +1,25 @@
1
+ module Referrer
2
+ module ControllerAdditions
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ before_action :link_referrer_user_to_app_user
7
+
8
+ def link_referrer_user_to_app_user
9
+ current_object = send(Referrer.current_user_method_name)
10
+ if current_object && referrer_user && !referrer_user.linked_with?(current_object)
11
+ referrer_user.link_with(current_object)
12
+ end
13
+ end
14
+
15
+ def referrer_user
16
+ @referrer ||= begin
17
+ if cookies[:referrer_user].present?
18
+ obj = JSON.parse(cookies[:referrer_user])
19
+ Referrer::User.where(id: obj['id'], token: obj['token']).first
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,35 @@
1
+ module Referrer
2
+ module OwnerModelAdditions
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ has_many :referrer_users_main_app_users, class_name: 'Referrer::UsersMainAppUser', as: :main_app_user
7
+
8
+ def referrer_users
9
+ referrer_users_main_app_users.includes(:user).map{|relation| relation.user}
10
+ end
11
+
12
+ def referrer_sources
13
+ Referrer::Source.where(
14
+ session_id: Referrer::Session.where(user_id: referrer_users_main_app_users.pluck(:user_id)).pluck(:id))
15
+ end
16
+
17
+ def referrer_first_source
18
+ referrer_sources.first
19
+ end
20
+
21
+ def referrer_priority_source
22
+ referrer_sources.priority.last
23
+ end
24
+
25
+ def referrer_last_source
26
+ referrer_sources.last
27
+ end
28
+
29
+ def referrer_markups
30
+ Hash[{first: referrer_first_source, priority: referrer_priority_source,
31
+ last: referrer_last_source}.map{|k, v| [k, v.try(:to_markup).try(:symbolize_keys!)]}]
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,21 @@
1
+ module Referrer
2
+ module TrackedModelAdditions
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ has_one :tracked_relation, as: :trackable, class_name: 'Referrer::SourcesTrackedObject'
7
+
8
+ def referrer_link_with(r_user, linked_at: nil)
9
+ create_tracked_relation(user: r_user, linked_at: linked_at)
10
+ end
11
+
12
+ def referrer_link_with!(r_user, linked_at: nil)
13
+ create_tracked_relation!(user: r_user, linked_at: linked_at)
14
+ end
15
+
16
+ def referrer_markup
17
+ tracked_relation.try(:source).try(:to_markup).try(:symbolize_keys!)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,25 @@
1
+ module Referrer
2
+ module Statistics
3
+ class << self
4
+ def sources_markup(from, to)
5
+ Referrer::Source.where('created_at >= ? AND created_at <= ?', from, to).group_by do |source|
6
+ source.to_markup.symbolize_keys!
7
+ end.map do |markup, matches|
8
+ markup.merge({count: matches.size})
9
+ end
10
+ end
11
+
12
+ def tracked_objects_markup(from, to, type: nil)
13
+ result = Referrer::SourcesTrackedObject.where('linked_at >= ? AND linked_at <= ?', from, to).includes(:source)
14
+ result = result.where(trackable_type: type) if type.present?
15
+ result.map{|item| item.source}.select do |source|
16
+ source.present?
17
+ end.group_by do |source|
18
+ source.to_markup.symbolize_keys!
19
+ end.map do |markup, matches|
20
+ markup.merge({count: matches.size})
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,112 @@
1
+ module Referrer
2
+ class MarkupGenerator
3
+ UTM_KEYS = %w(utm_source utm_medium utm_campaign utm_content utm_term)
4
+ ORGANICS = [{host: 'search.daum.net', param: 'q'},
5
+ {host: 'search.naver.com', param: 'query'},
6
+ {host: 'search.yahoo.com', param: 'p'},
7
+ {host: /^(www\.)?google\.[a-z]+$/, param: 'q', display: 'google'},
8
+ {host: 'www.bing.com', param: 'q'},
9
+ {host: 'search.aol.com', params: 'q'},
10
+ {host: 'search.lycos.com', param: 'q'},
11
+ {host: 'edition.cnn.com', param: 'text'},
12
+ {host: 'index.about.com', param: 'q'},
13
+ {host: 'mamma.com', param: 'q'},
14
+ {host: 'ricerca.virgilio.it', param: 'qs'},
15
+ {host: 'www.baidu.com', param: 'wd'},
16
+ {host: /^(www\.)?yandex\.[a-z]+$/, param: 'text', display: 'yandex'},
17
+ {host: 'search.seznam.cz', param: 'oq'},
18
+ {host: 'www.search.com', param: 'q'},
19
+ {host: 'search.yam.com', param: 'k'},
20
+ {host: 'www.kvasir.no', param: 'q'},
21
+ {host: 'buscador.terra.com', param: 'query'},
22
+ {host: 'nova.rambler.ru', param: 'query'},
23
+ {host: 'go.mail.ru', param: 'q'},
24
+ {host: 'www.ask.com', param: 'q'},
25
+ {host: 'searches.globososo.com', param: 'q'},
26
+ {host: 'search.tut.by', param: 'query'}]
27
+
28
+ REFERRALS = [{host: /^(www\.)?t\.co$/, display: 'twitter.com'},
29
+ {host: /^(www\.)?plus\.url\.google\.com$/, display: 'plus.google.com'}]
30
+
31
+ attr_accessor :organics, :referrals, :utm_synonyms, :array_params_joiner
32
+
33
+ def initialize
34
+ @organics = ORGANICS
35
+ @referrals = REFERRALS
36
+ @utm_synonyms = UTM_KEYS.inject({}){|r, key| r.merge({key => []})}
37
+ @array_params_joiner = ', '
38
+ end
39
+
40
+ def generate(referrer, entry_point)
41
+ referrer_uri, entry_point_uri = *[referrer, entry_point].map{|url| URI(URI::encode(url || ''))}
42
+ referrer_params, entry_point_params = *[referrer_uri, entry_point_uri].map{|uri| uri_params(uri)}
43
+ prepare_result(utm(entry_point_params) || organic(referrer_uri, referrer_params) ||
44
+ referral(referrer_uri) || direct)
45
+ end
46
+
47
+ private
48
+
49
+ def uri_params(uri)
50
+ Rack::Utils.parse_query(uri.try(:query))
51
+ end
52
+
53
+ def base_result
54
+ UTM_KEYS.inject({}){|r, key| r.merge!(key => '(none)')}
55
+ end
56
+
57
+ def check_host(option, value)
58
+ case option
59
+ when String
60
+ option == value
61
+ when Regexp
62
+ option =~ value
63
+ else
64
+ false
65
+ end
66
+ end
67
+
68
+ def prepare_result(markup)
69
+ Hash[markup.map{|k, v| [k, v.is_a?(Array) ? v.join(array_params_joiner) : v]}].symbolize_keys
70
+ end
71
+
72
+ def utm(entry_point_params)
73
+ if (entry_point_params.keys & (UTM_KEYS + utm_synonyms.values.flatten)).present?
74
+ UTM_KEYS.inject(base_result) do |r, key|
75
+ values = if utm_synonyms[key.to_sym].present?
76
+ [].push(entry_point_params[key]).push([utm_synonyms[key.to_sym]].flatten.map do |synonym_key|
77
+ entry_point_params[synonym_key]
78
+ end)
79
+ else
80
+ [entry_point_params[key]]
81
+ end.flatten.compact.map{|value| URI::decode(value)}
82
+ values.present? ? r.merge!({key => values}) : r
83
+ end.merge('kind' => 'utm')
84
+ end
85
+ end
86
+
87
+ def organic(referrer_uri, referrer_params)
88
+ if referrer_uri.to_s.present?
89
+ current_organic = organics.detect{|organic| check_host(organic[:host], referrer_uri.host)}
90
+ base_result.merge!({'utm_source' => current_organic[:display] || current_organic[:host].split('.')[-2],
91
+ 'utm_medium' => 'organic',
92
+ 'utm_term' => referrer_params[current_organic[:param]] || '(none)',
93
+ 'kind' => 'organic'}) if current_organic.present?
94
+ end
95
+ end
96
+
97
+ def referral(referrer_uri)
98
+ if referrer_uri.to_s.present?
99
+ custom_referral = referrals.detect{|referral| check_host(referral[:host], referrer_uri.host)}
100
+ base_result.merge!(
101
+ 'utm_source' => custom_referral ? custom_referral[:display] : referrer_uri.host.gsub('www.', ''),
102
+ 'utm_medium' => 'referral',
103
+ 'utm_content' => URI::decode(referrer_uri.request_uri) || '(none)',
104
+ 'kind' => 'referral')
105
+ end
106
+ end
107
+
108
+ def direct
109
+ base_result.merge!('utm_source' => '(direct)', 'kind' => 'direct')
110
+ end
111
+ end
112
+ end