helios-tracker 0.0.0 → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a496bc5169788a1c1fc3655f928f7b493137b4421d2138666686b2d921a35802
4
- data.tar.gz: 7eb796537bca7f003082b13002bb6fb2b950c48d4a5698f9e53c6f4aa5e92a71
3
+ metadata.gz: bfbf5eff1c6c46819373a06aed86d64e3d0ed9b267fee8832c22ceabd5f0d618
4
+ data.tar.gz: 0c80b7f065b45b086bbfabfa91d3993bab8372cffc6638e100ab9070b5c59375
5
5
  SHA512:
6
- metadata.gz: 379ec3dd8d9c8cfe760fc853668f4c285381ee732984cd28904bd9ad66511d1cfba16066f178dc164cfc3b78381c5f525ec7cad0e7abc6548609b20cc5750c82
7
- data.tar.gz: 278e4be3f8374e22657b89fdfc9bf49fe011364f1102b4354e60adaf74ba81b1ac4cc2b370e16ab9cbedd112b1a4aa1a572e87b758ce00c6053ff442e7e7c53d
6
+ metadata.gz: 54e55ca996db4f656a856434275b31c9884f91721f999752e49af7c59cd76351430ccee6e857c899914b8ad049030c398b71f79ba2f0e244f3f762b30dda297c
7
+ data.tar.gz: 594d5ae967a43ce5dad2f644f9decd463b47c68af82675cff271a3d9a23b1bddbcd0fbf107611ed9b7c87b2b71745a04fb81c002cc7894b9319282373dececb6
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ helios-tracker-*.gem
data/Gemfile.lock ADDED
@@ -0,0 +1,233 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ helios-tracker (0.0.0)
5
+ ffaker (~> 2.16)
6
+ rails (> 5.1)
7
+ universal-track-manager (~> 0.8)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ action_text-trix (2.1.18)
13
+ railties
14
+ actioncable (8.1.3)
15
+ actionpack (= 8.1.3)
16
+ activesupport (= 8.1.3)
17
+ nio4r (~> 2.0)
18
+ websocket-driver (>= 0.6.1)
19
+ zeitwerk (~> 2.6)
20
+ actionmailbox (8.1.3)
21
+ actionpack (= 8.1.3)
22
+ activejob (= 8.1.3)
23
+ activerecord (= 8.1.3)
24
+ activestorage (= 8.1.3)
25
+ activesupport (= 8.1.3)
26
+ mail (>= 2.8.0)
27
+ actionmailer (8.1.3)
28
+ actionpack (= 8.1.3)
29
+ actionview (= 8.1.3)
30
+ activejob (= 8.1.3)
31
+ activesupport (= 8.1.3)
32
+ mail (>= 2.8.0)
33
+ rails-dom-testing (~> 2.2)
34
+ actionpack (8.1.3)
35
+ actionview (= 8.1.3)
36
+ activesupport (= 8.1.3)
37
+ nokogiri (>= 1.8.5)
38
+ rack (>= 2.2.4)
39
+ rack-session (>= 1.0.1)
40
+ rack-test (>= 0.6.3)
41
+ rails-dom-testing (~> 2.2)
42
+ rails-html-sanitizer (~> 1.6)
43
+ useragent (~> 0.16)
44
+ actiontext (8.1.3)
45
+ action_text-trix (~> 2.1.15)
46
+ actionpack (= 8.1.3)
47
+ activerecord (= 8.1.3)
48
+ activestorage (= 8.1.3)
49
+ activesupport (= 8.1.3)
50
+ globalid (>= 0.6.0)
51
+ nokogiri (>= 1.8.5)
52
+ actionview (8.1.3)
53
+ activesupport (= 8.1.3)
54
+ builder (~> 3.1)
55
+ erubi (~> 1.11)
56
+ rails-dom-testing (~> 2.2)
57
+ rails-html-sanitizer (~> 1.6)
58
+ activejob (8.1.3)
59
+ activesupport (= 8.1.3)
60
+ globalid (>= 0.3.6)
61
+ activemodel (8.1.3)
62
+ activesupport (= 8.1.3)
63
+ activerecord (8.1.3)
64
+ activemodel (= 8.1.3)
65
+ activesupport (= 8.1.3)
66
+ timeout (>= 0.4.0)
67
+ activestorage (8.1.3)
68
+ actionpack (= 8.1.3)
69
+ activejob (= 8.1.3)
70
+ activerecord (= 8.1.3)
71
+ activesupport (= 8.1.3)
72
+ marcel (~> 1.0)
73
+ activesupport (8.1.3)
74
+ base64
75
+ bigdecimal
76
+ concurrent-ruby (~> 1.0, >= 1.3.1)
77
+ connection_pool (>= 2.2.5)
78
+ drb
79
+ i18n (>= 1.6, < 2)
80
+ json
81
+ logger (>= 1.4.2)
82
+ minitest (>= 5.1)
83
+ securerandom (>= 0.3)
84
+ tzinfo (~> 2.0, >= 2.0.5)
85
+ uri (>= 0.13.1)
86
+ base64 (0.3.0)
87
+ bigdecimal (4.1.2)
88
+ builder (3.3.0)
89
+ concurrent-ruby (1.3.6)
90
+ connection_pool (3.0.2)
91
+ crass (1.0.6)
92
+ date (3.5.1)
93
+ drb (2.2.3)
94
+ erb (6.0.4)
95
+ erubi (1.13.1)
96
+ ffaker (2.25.0)
97
+ globalid (1.3.0)
98
+ activesupport (>= 6.1)
99
+ i18n (1.14.8)
100
+ concurrent-ruby (~> 1.0)
101
+ io-console (0.8.2)
102
+ irb (1.18.0)
103
+ pp (>= 0.6.0)
104
+ prism (>= 1.3.0)
105
+ rdoc (>= 4.0.0)
106
+ reline (>= 0.4.2)
107
+ json (2.19.5)
108
+ logger (1.7.0)
109
+ loofah (2.25.1)
110
+ crass (~> 1.0.2)
111
+ nokogiri (>= 1.12.0)
112
+ mail (2.9.0)
113
+ logger
114
+ mini_mime (>= 0.1.1)
115
+ net-imap
116
+ net-pop
117
+ net-smtp
118
+ marcel (1.1.0)
119
+ mini_mime (1.1.5)
120
+ minitest (6.0.6)
121
+ drb (~> 2.0)
122
+ prism (~> 1.5)
123
+ net-imap (0.6.4)
124
+ date
125
+ net-protocol
126
+ net-pop (0.1.2)
127
+ net-protocol
128
+ net-protocol (0.2.2)
129
+ timeout
130
+ net-smtp (0.5.1)
131
+ net-protocol
132
+ nio4r (2.7.5)
133
+ nokogiri (1.19.3-aarch64-linux-gnu)
134
+ racc (~> 1.4)
135
+ nokogiri (1.19.3-aarch64-linux-musl)
136
+ racc (~> 1.4)
137
+ nokogiri (1.19.3-arm-linux-gnu)
138
+ racc (~> 1.4)
139
+ nokogiri (1.19.3-arm-linux-musl)
140
+ racc (~> 1.4)
141
+ nokogiri (1.19.3-arm64-darwin)
142
+ racc (~> 1.4)
143
+ nokogiri (1.19.3-x86_64-darwin)
144
+ racc (~> 1.4)
145
+ nokogiri (1.19.3-x86_64-linux-gnu)
146
+ racc (~> 1.4)
147
+ nokogiri (1.19.3-x86_64-linux-musl)
148
+ racc (~> 1.4)
149
+ pp (0.6.3)
150
+ prettyprint
151
+ prettyprint (0.2.0)
152
+ prism (1.9.0)
153
+ psych (5.3.1)
154
+ date
155
+ stringio
156
+ racc (1.8.1)
157
+ rack (3.2.6)
158
+ rack-session (2.1.2)
159
+ base64 (>= 0.1.0)
160
+ rack (>= 3.0.0)
161
+ rack-test (2.2.0)
162
+ rack (>= 1.3)
163
+ rackup (2.3.1)
164
+ rack (>= 3)
165
+ rails (8.1.3)
166
+ actioncable (= 8.1.3)
167
+ actionmailbox (= 8.1.3)
168
+ actionmailer (= 8.1.3)
169
+ actionpack (= 8.1.3)
170
+ actiontext (= 8.1.3)
171
+ actionview (= 8.1.3)
172
+ activejob (= 8.1.3)
173
+ activemodel (= 8.1.3)
174
+ activerecord (= 8.1.3)
175
+ activestorage (= 8.1.3)
176
+ activesupport (= 8.1.3)
177
+ bundler (>= 1.15.0)
178
+ railties (= 8.1.3)
179
+ rails-dom-testing (2.3.0)
180
+ activesupport (>= 5.0.0)
181
+ minitest
182
+ nokogiri (>= 1.6)
183
+ rails-html-sanitizer (1.7.0)
184
+ loofah (~> 2.25)
185
+ nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
186
+ railties (8.1.3)
187
+ actionpack (= 8.1.3)
188
+ activesupport (= 8.1.3)
189
+ irb (~> 1.13)
190
+ rackup (>= 1.0.0)
191
+ rake (>= 12.2)
192
+ thor (~> 1.0, >= 1.2.2)
193
+ tsort (>= 0.2)
194
+ zeitwerk (~> 2.6)
195
+ rake (13.4.2)
196
+ rdoc (7.2.0)
197
+ erb
198
+ psych (>= 4.0.0)
199
+ tsort
200
+ reline (0.6.3)
201
+ io-console (~> 0.5)
202
+ securerandom (0.4.1)
203
+ stringio (3.2.0)
204
+ thor (1.5.0)
205
+ timeout (0.6.1)
206
+ tsort (0.2.0)
207
+ tzinfo (2.0.6)
208
+ concurrent-ruby (~> 1.0)
209
+ universal-track-manager (0.8.0.1)
210
+ rails (> 5.1)
211
+ uri (1.1.1)
212
+ useragent (0.16.11)
213
+ websocket-driver (0.8.0)
214
+ base64
215
+ websocket-extensions (>= 0.1.0)
216
+ websocket-extensions (0.1.5)
217
+ zeitwerk (2.7.5)
218
+
219
+ PLATFORMS
220
+ aarch64-linux-gnu
221
+ aarch64-linux-musl
222
+ arm-linux-gnu
223
+ arm-linux-musl
224
+ arm64-darwin
225
+ x86_64-darwin
226
+ x86_64-linux-gnu
227
+ x86_64-linux-musl
228
+
229
+ DEPENDENCIES
230
+ helios-tracker!
231
+
232
+ BUNDLED WITH
233
+ 2.6.9
data/README.md ADDED
@@ -0,0 +1,197 @@
1
+ # Helios Tracker
2
+
3
+ A Rails engine that exposes your app's user, visit, and email-suppression data as JSON API endpoints for [Helios Crusader](https://heliosflow.ai/) to consume.
4
+
5
+ Helios pulls from these endpoints daily to track signups, logins, click-throughs, and blocked emails — automatically moving targets from cold to warm to hot based on engagement.
6
+
7
+ ## Requirements
8
+
9
+ - Rails >= 5.1
10
+ - Ruby >= 2.7
11
+ - [Universal Track Manager](https://github.com/jasonfb/universal_track_manager) (installed automatically)
12
+
13
+ ## Installation
14
+
15
+ Add to your Gemfile:
16
+
17
+ ```ruby
18
+ gem "helios-tracker"
19
+ ```
20
+
21
+ Then run:
22
+
23
+ ```sh
24
+ bundle install
25
+ rails generate helios_tracker:install
26
+ rails db:migrate
27
+ ```
28
+
29
+ The install generator will:
30
+
31
+ 1. Install and configure Universal Track Manager (visit tracking)
32
+ 2. Add `include UniversalTrackManagerConcern` to your `ApplicationController`
33
+ 3. Create a `blocked_emails` migration
34
+ 4. Generate `config/initializers/helios_tracker.rb`
35
+ 5. Mount the engine in your routes
36
+
37
+ If Universal Track Manager is already installed, the generator will detect it and skip that step.
38
+
39
+ ### Generator Options
40
+
41
+ ```sh
42
+ # Specify your User model class (default: "User")
43
+ rails generate helios_tracker:install --user-class=Account
44
+
45
+ # Skip UTM installation if already set up
46
+ rails generate helios_tracker:install --skip-utm
47
+ ```
48
+
49
+ ## Configuration
50
+
51
+ Set your API key in your environment (`.env`, credentials, or server config):
52
+
53
+ ```
54
+ HELIOS_TRACKER_API_KEY=your_secret_key_here
55
+ ```
56
+
57
+ Then edit `config/initializers/helios_tracker.rb`:
58
+
59
+ ```ruby
60
+ HeliosTracker.configure do |config|
61
+ # ---- Users ----
62
+
63
+ config.user_class_name = "User"
64
+
65
+ # Return users updated since query_start.
66
+ # Receives (query_start, params). Must return an ActiveRecord relation.
67
+ config.user_scope = ->(query_start, params) {
68
+ User.where.not(email: "")
69
+ .where("updated_at > ?", query_start)
70
+ }
71
+
72
+ # Map API field names to model attributes or lambdas.
73
+ # :email is required. All others are optional.
74
+ config.user_fields = {
75
+ email: :email,
76
+ created_at: :created_at,
77
+ source_ip: :source_ip,
78
+ login_count: :login_count,
79
+ accounts_owned_count: ->(user) { user.accounts_owned_count },
80
+ free_accounts_count: ->(user) { user.free_accounts_count },
81
+ unsubscribe_nonce: :unsubscribe_nonce,
82
+ first_unconfirmed_visit_id: :first_unconfirmed_visit_id,
83
+ login_attempt_count: :login_attempt_count,
84
+ app_open_days_count: :app_open_days_count,
85
+ }
86
+
87
+ # ---- Visits ----
88
+
89
+ config.visit_scope = ->(query_start, params) {
90
+ UniversalTrackManager::Visit.where.not(hmid: nil)
91
+ .where("updated_at > ?", query_start)
92
+ }
93
+
94
+ # :hmid is required.
95
+ config.visit_fields = {
96
+ hmid: :hmid,
97
+ visited_download_page: ->(visit) { visit.visited_download_page? },
98
+ }
99
+
100
+ # ---- Blocked Emails ----
101
+
102
+ config.blocked_email_class_name = "BlockedEmail"
103
+
104
+ config.blocked_email_scope = ->(query_start, params) {
105
+ BlockedEmail.where("created_at > ?", query_start)
106
+ }
107
+
108
+ config.blocked_email_fields = {
109
+ email: :email,
110
+ source: :source,
111
+ }
112
+
113
+ end
114
+ ```
115
+
116
+ ### Field Mapping
117
+
118
+ Each field in `user_fields`, `visit_fields`, and `blocked_email_fields` can be either:
119
+
120
+ - **A symbol** — calls that method on the record (e.g., `:email` calls `user.email`)
121
+ - **A lambda** — receives the record and returns a value (e.g., `->(user) { user.accounts.count }`)
122
+
123
+ Fields you don't include in the hash are silently omitted from the API response.
124
+
125
+ ### Scoping with Lambdas
126
+
127
+ The `user_scope`, `visit_scope`, and `blocked_email_scope` lambdas receive two arguments:
128
+
129
+ | Argument | Description |
130
+ |---------------|-------------------------------------------------------|
131
+ | `query_start` | A `YYYY-MM-DD` date string sent by Helios |
132
+ | `params` | The full request params (includes `domain_name`, etc.) |
133
+
134
+ Use these to filter your records however your app requires:
135
+
136
+ ```ruby
137
+ # Multi-domain example
138
+ config.user_scope = ->(query_start, params) {
139
+ domain = Domain.find_by(name: params[:domain_name])
140
+ User.where.not(email: "")
141
+ .where(domain_id: domain&.id)
142
+ .where("updated_at > ?", query_start)
143
+ }
144
+ ```
145
+
146
+ ## API Endpoints
147
+
148
+ All endpoints require authentication via `HELIOS_TRACKER_API_KEY`, passed as either:
149
+
150
+ - **Header:** `Authorization: Bearer <API_KEY>`
151
+ - **Query parameter:** `?api_key=<API_KEY>`
152
+
153
+ ### GET /api/all_users.json
154
+
155
+ Returns users updated since `query_start`.
156
+
157
+ | Parameter | Required | Description |
158
+ |------------- |----------|--------------------------------------|
159
+ | `query_start`| yes | `YYYY-MM-DD` — records from this date onward |
160
+ | `domain_name`| no | Domain filter (if your app is multi-domain) |
161
+ | `api_key` | yes | Shared API key |
162
+
163
+ ### GET /api/all_visits.json
164
+
165
+ Returns visits with a non-null `hmid` since `query_start`.
166
+
167
+ | Parameter | Required | Description |
168
+ |------------- |----------|--------------------------------------|
169
+ | `query_start`| yes | `YYYY-MM-DD` — visits from this date onward |
170
+ | `api_key` | yes | Shared API key |
171
+
172
+ ### GET /api/blocked_emails.json
173
+
174
+ Returns suppressed emails (bounces, unsubscribes) since `query_start`.
175
+
176
+ | Parameter | Required | Description |
177
+ |------------- |----------|--------------------------------------|
178
+ | `query_start`| yes | `YYYY-MM-DD` — blocks from this date onward |
179
+ | `api_key` | yes | Shared API key |
180
+
181
+ ## How It Works
182
+
183
+ Helios calls your endpoints once per day (9:00 AM UTC). Each call includes a `query_start` date — your scopes should return records created or updated on or after that date.
184
+
185
+ **Important for window-based fields:** `login_count`, `accounts_owned_count`, and `app_open_days_count` should return only counts within the `query_start..now` window. Helios accumulates these across successive pulls. Returning lifetime totals will result in double-counting.
186
+
187
+ Helios handles duplicates gracefully (find-or-create logic), so returning the same records on a re-request is safe.
188
+
189
+ ## License
190
+
191
+ MIT License. See [LICENSE](LICENSE) for details.
192
+
193
+ ## Links
194
+
195
+ - [Helios Flow](https://heliosflow.ai/)
196
+ - [Universal Track Manager](https://github.com/jasonfb/universal_track_manager)
197
+ - [Source Code](https://github.com/heliosdev-shop/helios-tracker)
@@ -0,0 +1,17 @@
1
+ module HeliosTrackerConcern
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ before_action :track_hmid
6
+ end
7
+
8
+ private
9
+
10
+ def track_hmid
11
+ return unless params['hmid'].present?
12
+ return unless session['visit_id'].present?
13
+
14
+ visit = UniversalTrackManager::Visit.find_by(id: session['visit_id'])
15
+ visit&.update(hmid: params['hmid'])
16
+ end
17
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HeliosTracker
4
+ module Api
5
+ class AllUsersController < BaseController
6
+ def index
7
+ return unless require_query_start!
8
+
9
+ config = HeliosTracker.config
10
+
11
+ if config.user_scope.nil?
12
+ render json: { error: 'HeliosTracker.user_scope is not configured' }, status: :internal_server_error
13
+ return
14
+ end
15
+
16
+ users = config.user_scope.call(query_start, params)
17
+ render json: users.map { |user| config.serialize_record(user, config.user_fields) }
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HeliosTracker
4
+ module Api
5
+ class AllVisitsController < BaseController
6
+ def index
7
+ return unless require_query_start!
8
+
9
+ config = HeliosTracker.config
10
+
11
+ if config.visit_scope.nil?
12
+ render json: { error: 'HeliosTracker.visit_scope is not configured' }, status: :internal_server_error
13
+ return
14
+ end
15
+
16
+ visits = config.visit_scope.call(query_start, params)
17
+ render json: visits.map { |visit| config.serialize_record(visit, config.visit_fields) }
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HeliosTracker
4
+ module Api
5
+ class BaseController < ActionController::API
6
+ before_action :authenticate_api!
7
+
8
+ private
9
+
10
+ def authenticate_api!
11
+ api_key = ENV['HELIOS_TRACKER_API_KEY']
12
+
13
+ if api_key.blank?
14
+ raise "No HELIOS_TRACKER_API_KEY set in environment. Set this in your .env or production config."
15
+ end
16
+
17
+ header_token = request.headers['Authorization']&.sub(/^Bearer\s+/, '')
18
+
19
+ if header_token != api_key && params[:api_key] != api_key
20
+ render json: { error: 'Unauthorized' }, status: :unauthorized
21
+ end
22
+ end
23
+
24
+ def query_start
25
+ params[:query_start]
26
+ end
27
+
28
+ def require_query_start!
29
+ if query_start.blank?
30
+ render json: { error: 'No `query_start` date specified' }, status: :bad_request
31
+ false
32
+ else
33
+ true
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HeliosTracker
4
+ module Api
5
+ class BlockedEmailsController < BaseController
6
+ def index
7
+ return unless require_query_start!
8
+
9
+ config = HeliosTracker.config
10
+
11
+ if config.blocked_email_scope.nil?
12
+ render json: []
13
+ return
14
+ end
15
+
16
+ records = config.blocked_email_scope.call(query_start, params)
17
+ render json: records.map { |record| config.serialize_record(record, config.blocked_email_fields) }
18
+ end
19
+ end
20
+ end
21
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,7 @@
1
+ HeliosTracker::Engine.routes.draw do
2
+ namespace :api do
3
+ resources :all_users, only: [:index]
4
+ resources :all_visits, only: [:index]
5
+ resources :blocked_emails, only: [:index]
6
+ end
7
+ end
@@ -0,0 +1,118 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+
4
+ module HeliosTracker
5
+ class InstallGenerator < Rails::Generators::Base
6
+ include Rails::Generators::Migration
7
+
8
+ source_root File.expand_path('templates', __dir__)
9
+
10
+ class_option :user_class, type: :string, default: "User",
11
+ desc: "The name of your User model class"
12
+
13
+ class_option :skip_utm, type: :boolean, default: false,
14
+ desc: "Skip Universal Track Manager installation"
15
+
16
+ def self.next_migration_number(path)
17
+ # Find the highest existing migration number and increment by 1,
18
+ # avoiding collisions with UTM migrations created in the same second.
19
+ existing = Dir.glob("#{path}/[0-9]*_*.rb").map { |f| File.basename(f).split("_", 2).first.to_i }
20
+ candidate = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i
21
+ [candidate, *existing].max + 1
22
+ end
23
+
24
+ def install_universal_track_manager
25
+ if options[:skip_utm]
26
+ say "Skipping Universal Track Manager installation (--skip-utm)", :yellow
27
+ return
28
+ end
29
+
30
+ if File.exist?("config/initializers/universal_track_manager.rb")
31
+ say "Universal Track Manager initializer already exists, skipping UTM install", :yellow
32
+ else
33
+ say "Installing Universal Track Manager...", :green
34
+ generate "universal_track_manager:install"
35
+ end
36
+ end
37
+
38
+ def add_utm_concern_to_application_controller
39
+ if options[:skip_utm]
40
+ say "Skipping UTM concern injection (--skip-utm)", :yellow
41
+ return
42
+ end
43
+
44
+ app_controller = "app/controllers/application_controller.rb"
45
+ if File.exist?(app_controller)
46
+ content = File.read(app_controller)
47
+ if content.include?("UniversalTrackManagerConcern")
48
+ say "UniversalTrackManagerConcern already included in ApplicationController", :yellow
49
+ else
50
+ inject_into_class app_controller, "ApplicationController",
51
+ " include UniversalTrackManagerConcern\n"
52
+ say "Added UniversalTrackManagerConcern to ApplicationController", :green
53
+ end
54
+ else
55
+ say "Could not find #{app_controller} — please add `include UniversalTrackManagerConcern` manually", :red
56
+ end
57
+ end
58
+
59
+ def add_helios_tracker_concern_to_application_controller
60
+ app_controller = "app/controllers/application_controller.rb"
61
+ if File.exist?(app_controller)
62
+ content = File.read(app_controller)
63
+ if content.include?("HeliosTrackerConcern")
64
+ say "HeliosTrackerConcern already included in ApplicationController", :yellow
65
+ else
66
+ inject_into_class app_controller, "ApplicationController",
67
+ " include HeliosTrackerConcern\n"
68
+ say "Added HeliosTrackerConcern to ApplicationController", :green
69
+ end
70
+ else
71
+ say "Could not find #{app_controller} — please add `include HeliosTrackerConcern` manually", :red
72
+ end
73
+ end
74
+
75
+ def create_add_hmid_to_visits_migration
76
+ migration_template "add_hmid_to_visits.rb.erb",
77
+ "db/migrate/add_hmid_to_visits.rb"
78
+ end
79
+
80
+ def create_blocked_emails_migration
81
+ migration_template "create_blocked_emails.rb.erb",
82
+ "db/migrate/create_blocked_emails.rb"
83
+ end
84
+
85
+ def create_initializer
86
+ @user_class = options[:user_class]
87
+ template "helios_tracker.rb.erb", "config/initializers/helios_tracker.rb"
88
+ end
89
+
90
+ def mount_engine_routes
91
+ route_file = "config/routes.rb"
92
+ content = File.read(route_file)
93
+ if content.include?("mount HeliosTracker::Engine")
94
+ say "HeliosTracker engine already mounted in routes", :yellow
95
+ else
96
+ route "mount HeliosTracker::Engine, at: '/'"
97
+ say "Mounted HeliosTracker::Engine at '/' in routes", :green
98
+ end
99
+ end
100
+
101
+ def print_post_install
102
+ say ""
103
+ say "=" * 60, :green
104
+ say "Helios Tracker installed successfully!", :green
105
+ say "=" * 60, :green
106
+ say ""
107
+ say "Next steps:", :yellow
108
+ say " 1. Run `rails db:migrate`"
109
+ say " 2. Set HELIOS_TRACKER_API_KEY in your environment (.env or production config)"
110
+ say " 3. Edit config/initializers/helios_tracker.rb to configure your user scope and fields"
111
+ say " 4. Your API endpoints are now available at:"
112
+ say " GET /api/all_users.json"
113
+ say " GET /api/all_visits.json"
114
+ say " GET /api/blocked_emails.json"
115
+ say ""
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,6 @@
1
+ class AddHmidToVisits < ActiveRecord::Migration<%= "[#{ActiveRecord::Migration.current_version}]" %>
2
+ def change
3
+ add_column :visits, :hmid, :string
4
+ add_index :visits, :hmid
5
+ end
6
+ end
@@ -0,0 +1,12 @@
1
+ class CreateBlockedEmails < ActiveRecord::Migration<%= "[#{ActiveRecord::Migration.current_version}]" %>
2
+ def change
3
+ create_table :blocked_emails, id: :uuid do |t|
4
+ t.string :email, null: false
5
+ t.string :source, null: false
6
+ t.timestamps
7
+ end
8
+
9
+ add_index :blocked_emails, :email
10
+ add_index :blocked_emails, :created_at
11
+ end
12
+ end
@@ -0,0 +1,69 @@
1
+ HeliosTracker.configure do |config|
2
+ # -----------------------------------------------------------------------
3
+ # User Configuration
4
+ # -----------------------------------------------------------------------
5
+
6
+ # The name of your User model class
7
+ config.user_class_name = "<%= @user_class %>"
8
+
9
+ # A lambda that returns users to expose via GET /api/all_users.json
10
+ # Receives (query_start, params) — query_start is a YYYY-MM-DD string.
11
+ # Must return an ActiveRecord relation.
12
+ config.user_scope = ->(query_start, params) {
13
+ <%= @user_class %>.where.not(email: "")
14
+ .where("updated_at > ?", query_start)
15
+ }
16
+
17
+ # Map API response field names to your model's attributes or lambdas.
18
+ # :email is required by the Helios API. All other fields are optional —
19
+ # simply remove any lines you don't need.
20
+ config.user_fields = {
21
+ email: :email,
22
+ created_at: :created_at,
23
+ # source_ip: :source_ip,
24
+ # first_unconfirmed_visit_id: :first_unconfirmed_visit_id,
25
+ # login_attempt_count: :login_attempt_count,
26
+ # login_count: :login_count,
27
+ # accounts_owned_count: ->(user) { user.accounts_owned_count },
28
+ # free_accounts_count: ->(user) { user.free_accounts_count },
29
+ # unsubscribe_nonce: :unsubscribe_nonce,
30
+ # app_open_days_count: :app_open_days_count,
31
+ }
32
+
33
+ # -----------------------------------------------------------------------
34
+ # Visit Configuration (requires Universal Track Manager)
35
+ # -----------------------------------------------------------------------
36
+
37
+ # A lambda that returns visits to expose via GET /api/all_visits.json
38
+ # Visits with a nil hmid are skipped by Helios, so filter them out here.
39
+ config.visit_scope = ->(query_start, params) {
40
+ UniversalTrackManager::Visit.where.not(hmid: nil)
41
+ .where("updated_at > ?", query_start)
42
+ }
43
+
44
+ # Map API response field names to your visit model's attributes or lambdas.
45
+ # :hmid is required by the Helios API.
46
+ config.visit_fields = {
47
+ hmid: :hmid,
48
+ # visited_download_page: ->(visit) { visit.visited_download_page? },
49
+ }
50
+
51
+ # -----------------------------------------------------------------------
52
+ # Blocked Emails Configuration
53
+ # -----------------------------------------------------------------------
54
+
55
+ # The class used for blocked/suppressed emails
56
+ config.blocked_email_class_name = "BlockedEmail"
57
+
58
+ # A lambda that returns blocked emails via GET /api/blocked_emails.json
59
+ config.blocked_email_scope = ->(query_start, params) {
60
+ BlockedEmail.where("created_at > ?", query_start)
61
+ }
62
+
63
+ config.blocked_email_fields = {
64
+ email: :email,
65
+ source: :source,
66
+ created_at: :created_at,
67
+ }
68
+
69
+ end
@@ -0,0 +1 @@
1
+ require "helios_tracker"
@@ -0,0 +1,124 @@
1
+ module HeliosTracker
2
+ class Configuration
3
+ # Required: the User class name as a string, e.g. "User"
4
+ attr_accessor :user_class_name
5
+
6
+ # Required: a lambda that receives (query_start, params) and returns an
7
+ # ActiveRecord relation of user records to expose via the API.
8
+ #
9
+ # Example:
10
+ # config.user_scope = ->(query_start, params) {
11
+ # User.where.not(email: "")
12
+ # .where("updated_at > ?", query_start)
13
+ # }
14
+ attr_accessor :user_scope
15
+
16
+ # Optional: a lambda that receives (query_start, params) and returns an
17
+ # ActiveRecord relation of visit records. Defaults to UTM visits with hmid.
18
+ #
19
+ # Example:
20
+ # config.visit_scope = ->(query_start, params) {
21
+ # UniversalTrackManager::Visit.where.not(hmid: nil)
22
+ # .where("updated_at > ?", query_start)
23
+ # }
24
+ attr_accessor :visit_scope
25
+
26
+ # Optional: a lambda that receives (query_start, params) and returns an
27
+ # ActiveRecord relation of blocked email records.
28
+ #
29
+ # Example:
30
+ # config.blocked_email_scope = ->(query_start, params) {
31
+ # BlockedEmail.where("created_at > ?", query_start)
32
+ # }
33
+ attr_accessor :blocked_email_scope
34
+
35
+ # Optional user fields — map API field names to model attributes or lambdas.
36
+ # :email is always required and included automatically.
37
+ #
38
+ # Example:
39
+ # config.user_fields = {
40
+ # email: :email,
41
+ # created_at: :created_at,
42
+ # source_ip: ->(user) { user.try(:source_ip) },
43
+ # first_unconfirmed_visit_id: :first_unconfirmed_visit_id,
44
+ # login_attempt_count: :login_attempt_count,
45
+ # login_count: :login_count,
46
+ # accounts_owned_count: ->(user) { user.accounts_owned_count },
47
+ # free_accounts_count: ->(user) { user.free_accounts_count },
48
+ # unsubscribe_nonce: :unsubscribe_nonce,
49
+ # app_open_days_count: :app_open_days_count,
50
+ # }
51
+ attr_accessor :user_fields
52
+
53
+ # Optional visit fields — map API field names to model attributes or lambdas.
54
+ # :hmid is always required and included automatically.
55
+ #
56
+ # Example:
57
+ # config.visit_fields = {
58
+ # hmid: :hmid,
59
+ # visited_download_page: ->(visit) { visit.visited_download_page? },
60
+ # }
61
+ attr_accessor :visit_fields
62
+
63
+ # Optional blocked email fields — map API field names to model attributes or lambdas.
64
+ # :email and :source are always required and included automatically.
65
+ #
66
+ # Example:
67
+ # config.blocked_email_fields = {
68
+ # email: :email,
69
+ # source: :source,
70
+ # created_at: :created_at,
71
+ # }
72
+ attr_accessor :blocked_email_fields
73
+
74
+ # Optional: the class name for blocked emails, e.g. "BlockedEmail"
75
+ attr_accessor :blocked_email_class_name
76
+
77
+
78
+ def initialize
79
+ @user_class_name = "User"
80
+ @blocked_email_class_name = nil
81
+
82
+ @user_fields = {
83
+ email: :email,
84
+ created_at: :created_at,
85
+ }
86
+
87
+ @visit_fields = {
88
+ hmid: :hmid,
89
+ }
90
+
91
+ @blocked_email_fields = {
92
+ email: :email,
93
+ source: :source,
94
+ created_at: :created_at,
95
+ }
96
+
97
+ # Default scopes
98
+ @user_scope = nil
99
+ @visit_scope = nil
100
+ @blocked_email_scope = nil
101
+ end
102
+
103
+ def user_class
104
+ @user_class_name.constantize
105
+ end
106
+
107
+ def blocked_email_class
108
+ return nil unless @blocked_email_class_name
109
+ @blocked_email_class_name.constantize
110
+ end
111
+
112
+ # Serialize a single record using a fields hash.
113
+ # Fields can be symbols (attribute names) or lambdas.
114
+ def serialize_record(record, fields)
115
+ fields.each_with_object({}) do |(key, mapping), hash|
116
+ hash[key] = if mapping.respond_to?(:call)
117
+ mapping.call(record)
118
+ else
119
+ record.public_send(mapping)
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -1,5 +1,5 @@
1
1
  module HeliosTracker
2
2
  class Version
3
- CURRENT = '0.0.0'
3
+ CURRENT = '0.0.1'
4
4
  end
5
5
  end
@@ -0,0 +1,27 @@
1
+ require "helios_tracker/version"
2
+ require "helios_tracker/configuration"
3
+
4
+ # UTM's entry point (universal-track-manager.rb) loads models before defining
5
+ # the module, which breaks on Rails 8+. We pre-define the module so models
6
+ # can reference UniversalTrackManager:: constants, then load the full entry
7
+ # point which brings in models, concerns, and finally reopens the module
8
+ # to add .configure and friends.
9
+ module UniversalTrackManager; end unless defined?(UniversalTrackManager)
10
+ require "universal-track-manager"
11
+
12
+ module HeliosTracker
13
+ class << self
14
+ attr_accessor :configuration
15
+
16
+ def configure
17
+ self.configuration ||= Configuration.new
18
+ yield(configuration)
19
+ end
20
+
21
+ def config
22
+ configuration || Configuration.new
23
+ end
24
+ end
25
+ end
26
+
27
+ require "helios_tracker/engine" if defined?(Rails::Engine)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: helios-tracker
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jason Fleetwood-Boldt
@@ -39,19 +39,19 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '2.16'
41
41
  - !ruby/object:Gem::Dependency
42
- name: universal_track_manager
42
+ name: universal-track-manager
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '2.16'
47
+ version: '0.8'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '2.16'
54
+ version: '0.8'
55
55
  description: Tracks click rates, signups, logins, and daily app interactions via REST
56
56
  API. Targets automatically move from cold to warm or warm to hot as they engage
57
57
  — increasing Crusader send frequency in real time.
@@ -60,9 +60,25 @@ executables: []
60
60
  extensions: []
61
61
  extra_rdoc_files: []
62
62
  files:
63
+ - ".gitignore"
63
64
  - ".ruby-version"
64
65
  - Gemfile
66
+ - Gemfile.lock
67
+ - README.md
68
+ - app/controllers/concerns/helios_tracker_concern.rb
69
+ - app/controllers/helios_tracker/api/all_users_controller.rb
70
+ - app/controllers/helios_tracker/api/all_visits_controller.rb
71
+ - app/controllers/helios_tracker/api/base_controller.rb
72
+ - app/controllers/helios_tracker/api/blocked_emails_controller.rb
65
73
  - config/database.yml
74
+ - config/routes.rb
75
+ - lib/generators/helios_tracker/install_generator.rb
76
+ - lib/generators/helios_tracker/templates/add_hmid_to_visits.rb.erb
77
+ - lib/generators/helios_tracker/templates/create_blocked_emails.rb.erb
78
+ - lib/generators/helios_tracker/templates/helios_tracker.rb.erb
79
+ - lib/helios-tracker.rb
80
+ - lib/helios_tracker.rb
81
+ - lib/helios_tracker/configuration.rb
66
82
  - lib/helios_tracker/engine.rb
67
83
  - lib/helios_tracker/version.rb
68
84
  homepage: https://heliosflow.ai?utm_source=rubygems.org&utm_campaign=rubygems_link
@@ -80,7 +96,7 @@ post_install_message: |
80
96
  rails db:migrate
81
97
 
82
98
  Then set your API key in your environment:
83
- SELF_API_KEY=your_secret_key_here
99
+ HELIOS_TRACKER_API_KEY=your_secret_key_here
84
100
 
85
101
  Edit config/initializers/helios_tracker.rb to configure
86
102
  your user scope, fields, and visit tracking.