umami-read-models 0.1.0
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 +7 -0
- data/.rubocop.yml +16 -0
- data/CHANGELOG.md +21 -0
- data/LICENSE.txt +21 -0
- data/README.md +278 -0
- data/Rakefile +12 -0
- data/lib/umami/models/event_data.rb +26 -0
- data/lib/umami/models/report.rb +23 -0
- data/lib/umami/models/session.rb +22 -0
- data/lib/umami/models/session_data.rb +28 -0
- data/lib/umami/models/team.rb +16 -0
- data/lib/umami/models/team_user.rb +16 -0
- data/lib/umami/models/user.rb +20 -0
- data/lib/umami/models/version.rb +7 -0
- data/lib/umami/models/website.rb +28 -0
- data/lib/umami/models/website_event.rb +25 -0
- data/lib/umami/models.rb +53 -0
- data/sig/umami/models.rbs +6 -0
- metadata +92 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 2c2f6153e25093f6b47ff480dcad0305d3729725541ce61fe814c5551f52ad3f
|
4
|
+
data.tar.gz: e81abfd30bf2a212a5f8ca47847720a3760358c58cf5da14c659519e4d068c35
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4867b41518f09623a58584e9130aab9314d89366e9d97d0a5b8826d953156d20004ba3df88087e459bd0757e263331bb71bb14a149d6eb49caf6e8159ef63d15
|
7
|
+
data.tar.gz: 30676022f6642b95b55658364d072a1bb282e0df36c5c01e4f158cefac29af0465d7697b813b5b40814cba45c444bd99edfee818e1c3555a456d46b5eaa91b01
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 3.0
|
3
|
+
NewCops: enable
|
4
|
+
|
5
|
+
Style/StringLiterals:
|
6
|
+
EnforcedStyle: double_quotes
|
7
|
+
|
8
|
+
Style/StringLiteralsInInterpolation:
|
9
|
+
EnforcedStyle: double_quotes
|
10
|
+
|
11
|
+
Style/Documentation:
|
12
|
+
Exclude:
|
13
|
+
- 'test/**/*'
|
14
|
+
|
15
|
+
Style/ClassAndModuleChildren:
|
16
|
+
Enabled: false
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
|
+
|
8
|
+
## [Unreleased]
|
9
|
+
|
10
|
+
## [0.1.0] - 2024-01-26
|
11
|
+
|
12
|
+
### Added
|
13
|
+
- Initial release of umami-read-models gem
|
14
|
+
- Read-only ActiveRecord models for all Umami database tables
|
15
|
+
- Support for PostgreSQL connections
|
16
|
+
- Rails multi-database support with external configuration
|
17
|
+
- Comprehensive query scopes for analytics queries
|
18
|
+
- Full association mappings between models
|
19
|
+
- Configurable table prefix support
|
20
|
+
- Thread-safe connection management
|
21
|
+
- Documentation and usage examples
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2024 Andreas Zeitler
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,278 @@
|
|
1
|
+
# Umami::Models
|
2
|
+
|
3
|
+
A Ruby gem that provides read-only ActiveRecord models for accessing Umami Analytics data directly from Rails applications. This gem allows you to query Umami's database directly for analytics data, reports, and user information.
|
4
|
+
|
5
|
+
## Features
|
6
|
+
|
7
|
+
- Read-only ActiveRecord models for all Umami database tables
|
8
|
+
- Support for PostgreSQL connections
|
9
|
+
- Built-in query scopes for common analytics queries
|
10
|
+
- Association mappings between models
|
11
|
+
- Configurable table prefix support
|
12
|
+
- Thread-safe connection management
|
13
|
+
|
14
|
+
## Installation
|
15
|
+
|
16
|
+
Add this line to your application's Gemfile:
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
gem 'umami-read-models'
|
20
|
+
```
|
21
|
+
|
22
|
+
And then execute:
|
23
|
+
|
24
|
+
$ bundle install
|
25
|
+
|
26
|
+
Or install it yourself as:
|
27
|
+
|
28
|
+
$ gem install umami-read-models
|
29
|
+
|
30
|
+
## Configuration
|
31
|
+
|
32
|
+
### Setup with Rails Multi-Database Support
|
33
|
+
|
34
|
+
This gem is designed to work with Rails 6+ multiple database support. Configure your database in `config/database.yml`:
|
35
|
+
|
36
|
+
```yaml
|
37
|
+
# config/database.yml
|
38
|
+
production:
|
39
|
+
primary:
|
40
|
+
# Your main Rails database configuration
|
41
|
+
umami:
|
42
|
+
adapter: postgresql
|
43
|
+
host: <%= ENV['UMAMI_DB_HOST'] %>
|
44
|
+
port: <%= ENV['UMAMI_DB_PORT'] || 5432 %>
|
45
|
+
database: <%= ENV['UMAMI_DB_NAME'] %>
|
46
|
+
username: <%= ENV['UMAMI_DB_USER'] %>
|
47
|
+
password: <%= ENV['UMAMI_DB_PASSWORD'] %>
|
48
|
+
```
|
49
|
+
|
50
|
+
Then configure the gem in an initializer (e.g., `config/initializers/umami_read_models.rb`):
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
Umami::Models.configure do |config|
|
54
|
+
# Specify which database configuration to use
|
55
|
+
config.database = :umami
|
56
|
+
|
57
|
+
# Optional: Set a table prefix if your Umami tables use one
|
58
|
+
config.table_prefix = "umami_"
|
59
|
+
end
|
60
|
+
```
|
61
|
+
|
62
|
+
### Advanced Multi-Database Configuration
|
63
|
+
|
64
|
+
For read replicas:
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
Umami::Models.configure do |config|
|
68
|
+
config.database = { writing: :umami, reading: :umami_replica }
|
69
|
+
end
|
70
|
+
```
|
71
|
+
|
72
|
+
This uses Rails' built-in database roles. Even though the models are read-only, Rails still requires
|
73
|
+
a `:writing` connection to be defined. Both connections can point to the same database, or you can
|
74
|
+
use a read replica for the `:reading` connection for better performance.
|
75
|
+
|
76
|
+
## Usage
|
77
|
+
|
78
|
+
### Available Models
|
79
|
+
|
80
|
+
- `Umami::Models::User` - Umami users
|
81
|
+
- `Umami::Models::Website` - Tracked websites
|
82
|
+
- `Umami::Models::Session` - Visitor sessions
|
83
|
+
- `Umami::Models::WebsiteEvent` - Page views and custom events
|
84
|
+
- `Umami::Models::EventData` - Custom event data
|
85
|
+
- `Umami::Models::SessionData` - Session metadata
|
86
|
+
- `Umami::Models::Team` - Teams
|
87
|
+
- `Umami::Models::TeamUser` - Team memberships
|
88
|
+
- `Umami::Models::Report` - Saved reports
|
89
|
+
|
90
|
+
### Basic Queries
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
# Get all websites
|
94
|
+
websites = Umami::Models::Website.all
|
95
|
+
|
96
|
+
# Get active websites for a user
|
97
|
+
user_websites = Umami::Models::Website
|
98
|
+
.active
|
99
|
+
.by_user(user_id)
|
100
|
+
|
101
|
+
# Get recent sessions for a website
|
102
|
+
recent_sessions = Umami::Models::Session
|
103
|
+
.by_website(website_id)
|
104
|
+
.recent
|
105
|
+
.limit(100)
|
106
|
+
|
107
|
+
# Get page views for the last 7 days
|
108
|
+
page_views = Umami::Models::WebsiteEvent
|
109
|
+
.by_website(website_id)
|
110
|
+
.page_views
|
111
|
+
.by_date_range(7.days.ago, Time.current)
|
112
|
+
```
|
113
|
+
|
114
|
+
### Working with Sessions
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
# Get sessions by browser
|
118
|
+
chrome_sessions = Umami::Models::Session
|
119
|
+
.by_website(website_id)
|
120
|
+
.by_browser('Chrome')
|
121
|
+
.by_date_range(start_date, end_date)
|
122
|
+
|
123
|
+
# Get sessions by country
|
124
|
+
us_sessions = Umami::Models::Session
|
125
|
+
.by_website(website_id)
|
126
|
+
.by_country('US')
|
127
|
+
|
128
|
+
# Get session with events
|
129
|
+
session = Umami::Models::Session.find(session_id)
|
130
|
+
events = session.website_events.page_views
|
131
|
+
```
|
132
|
+
|
133
|
+
### Working with Events
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
# Get custom events
|
137
|
+
custom_events = Umami::Models::WebsiteEvent
|
138
|
+
.by_website(website_id)
|
139
|
+
.custom_events
|
140
|
+
.by_event_name('button_click')
|
141
|
+
|
142
|
+
# Get events with UTM parameters
|
143
|
+
campaign_events = Umami::Models::WebsiteEvent
|
144
|
+
.by_website(website_id)
|
145
|
+
.with_utm_campaign('summer_sale')
|
146
|
+
|
147
|
+
# Get event data
|
148
|
+
event = Umami::Models::WebsiteEvent.find(event_id)
|
149
|
+
event_data = event.event_data
|
150
|
+
```
|
151
|
+
|
152
|
+
### Analytics Queries
|
153
|
+
|
154
|
+
```ruby
|
155
|
+
# Get unique visitors (sessions) by day
|
156
|
+
daily_visitors = Umami::Models::Session
|
157
|
+
.by_website(website_id)
|
158
|
+
.group("DATE(created_at)")
|
159
|
+
.count
|
160
|
+
|
161
|
+
# Get top pages
|
162
|
+
top_pages = Umami::Models::WebsiteEvent
|
163
|
+
.by_website(website_id)
|
164
|
+
.page_views
|
165
|
+
.group(:url_path)
|
166
|
+
.order('count_all DESC')
|
167
|
+
.limit(10)
|
168
|
+
.count
|
169
|
+
|
170
|
+
# Get referrer domains
|
171
|
+
referrers = Umami::Models::WebsiteEvent
|
172
|
+
.by_website(website_id)
|
173
|
+
.where.not(referrer_domain: nil)
|
174
|
+
.group(:referrer_domain)
|
175
|
+
.order('count_all DESC')
|
176
|
+
.count
|
177
|
+
|
178
|
+
# Get browser statistics
|
179
|
+
browser_stats = Umami::Models::Session
|
180
|
+
.by_website(website_id)
|
181
|
+
.group(:browser)
|
182
|
+
.count
|
183
|
+
```
|
184
|
+
|
185
|
+
### Working with Reports
|
186
|
+
|
187
|
+
```ruby
|
188
|
+
# Get user reports
|
189
|
+
user_reports = Umami::Models::Report
|
190
|
+
.by_user(user_id)
|
191
|
+
.recent
|
192
|
+
|
193
|
+
# Get report with parsed parameters
|
194
|
+
report = Umami::Models::Report.find(report_id)
|
195
|
+
params = report.parsed_parameters
|
196
|
+
```
|
197
|
+
|
198
|
+
## Read-Only Protection
|
199
|
+
|
200
|
+
All models are read-only by default. Any attempt to create, update, or delete records will fail:
|
201
|
+
|
202
|
+
```ruby
|
203
|
+
# This will raise an error
|
204
|
+
website = Umami::Models::Website.new(name: "Test")
|
205
|
+
website.save # => raises ActiveRecord::ReadOnlyRecord
|
206
|
+
|
207
|
+
# This will also raise an error
|
208
|
+
Umami::Models::Website.find(id).update(name: "New Name")
|
209
|
+
```
|
210
|
+
|
211
|
+
## Advanced Usage
|
212
|
+
|
213
|
+
### Custom Queries
|
214
|
+
|
215
|
+
You can use all ActiveRecord query methods:
|
216
|
+
|
217
|
+
```ruby
|
218
|
+
# Complex query example
|
219
|
+
Umami::Models::WebsiteEvent
|
220
|
+
.joins(:session)
|
221
|
+
.where(website_id: website_id)
|
222
|
+
.where(sessions: { country: 'US' })
|
223
|
+
.where(created_at: 30.days.ago..Time.current)
|
224
|
+
.group(:url_path)
|
225
|
+
.having('COUNT(*) > ?', 100)
|
226
|
+
.pluck(:url_path, 'COUNT(*)')
|
227
|
+
```
|
228
|
+
|
229
|
+
### Raw SQL
|
230
|
+
|
231
|
+
For complex analytics queries, you can use raw SQL:
|
232
|
+
|
233
|
+
```ruby
|
234
|
+
results = Umami::Models::Base.connection.execute(<<-SQL)
|
235
|
+
SELECT
|
236
|
+
DATE(created_at) as date,
|
237
|
+
COUNT(DISTINCT session_id) as visitors,
|
238
|
+
COUNT(*) as page_views
|
239
|
+
FROM website_event
|
240
|
+
WHERE website_id = '#{website_id}'
|
241
|
+
AND created_at >= '#{30.days.ago}'
|
242
|
+
GROUP BY DATE(created_at)
|
243
|
+
ORDER BY date DESC
|
244
|
+
SQL
|
245
|
+
```
|
246
|
+
|
247
|
+
## Development
|
248
|
+
|
249
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
250
|
+
|
251
|
+
### Releasing a New Version
|
252
|
+
|
253
|
+
1. Update the version number in `lib/umami/models/version.rb`
|
254
|
+
2. Update the CHANGELOG.md with the new version and changes
|
255
|
+
3. Commit the changes: `git commit -am "Release version X.Y.Z"`
|
256
|
+
4. Create a tag: `git tag vX.Y.Z`
|
257
|
+
5. Push the changes and tag: `git push origin main --tags`
|
258
|
+
|
259
|
+
The GitHub Action will automatically:
|
260
|
+
- Run the test suite
|
261
|
+
- Build the gem
|
262
|
+
- Publish to RubyGems.org
|
263
|
+
- Create a GitHub release
|
264
|
+
|
265
|
+
**Note**: You need to set up the `RUBYGEMS_API_KEY` secret in your GitHub repository settings for automatic publishing to work.
|
266
|
+
|
267
|
+
### Manual Release
|
268
|
+
|
269
|
+
If you need to release manually:
|
270
|
+
|
271
|
+
```bash
|
272
|
+
gem build umami-read-models.gemspec
|
273
|
+
gem push umami-read-models-*.gem
|
274
|
+
```
|
275
|
+
|
276
|
+
## Contributing
|
277
|
+
|
278
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/azeitler/umami-read-models.
|
data/Rakefile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Umami
|
4
|
+
module Models
|
5
|
+
# Represents custom data associated with an event
|
6
|
+
class EventData < Base
|
7
|
+
self.table_name = "event_data"
|
8
|
+
self.primary_key = "event_data_id"
|
9
|
+
belongs_to :website, foreign_key: "website_id"
|
10
|
+
belongs_to :website_event, foreign_key: "website_event_id"
|
11
|
+
scope :by_website, ->(website_id) { where(website_id: website_id) }
|
12
|
+
scope :by_key, ->(key) { where(data_key: key) }
|
13
|
+
scope :string_type, -> { where(data_type: 1) }
|
14
|
+
scope :number_type, -> { where(data_type: 2) }
|
15
|
+
scope :date_type, -> { where(data_type: 3) }
|
16
|
+
scope :by_date_range, ->(start_date, end_date) { where(created_at: start_date..end_date) }
|
17
|
+
def value
|
18
|
+
case data_type
|
19
|
+
when 1 then string_value
|
20
|
+
when 2 then number_value
|
21
|
+
when 3 then date_value
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Umami
|
4
|
+
module Models
|
5
|
+
# Represents a saved report configuration
|
6
|
+
class Report < Base
|
7
|
+
self.table_name = "report"
|
8
|
+
self.primary_key = "report_id"
|
9
|
+
belongs_to :user, foreign_key: "user_id"
|
10
|
+
belongs_to :website, foreign_key: "website_id"
|
11
|
+
scope :by_type, ->(type) { where(type: type) }
|
12
|
+
scope :by_name, ->(name) { where(name: name) }
|
13
|
+
scope :by_user, ->(user_id) { where(user_id: user_id) }
|
14
|
+
scope :by_website, ->(website_id) { where(website_id: website_id) }
|
15
|
+
scope :recent, -> { order(created_at: :desc) }
|
16
|
+
def parsed_parameters
|
17
|
+
JSON.parse(parameters)
|
18
|
+
rescue JSON::ParserError
|
19
|
+
{}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Umami
|
4
|
+
module Models
|
5
|
+
# Represents a visitor session
|
6
|
+
class Session < Base
|
7
|
+
self.table_name = "session"
|
8
|
+
self.primary_key = "session_id"
|
9
|
+
|
10
|
+
has_many :website_events, class_name: "WebsiteEvent", foreign_key: "session_id"
|
11
|
+
has_many :session_data, class_name: "SessionData", foreign_key: "session_id"
|
12
|
+
|
13
|
+
scope :recent, -> { order(created_at: :desc) }
|
14
|
+
scope :by_website, ->(website_id) { where(website_id: website_id) }
|
15
|
+
scope :by_browser, ->(browser) { where(browser: browser) }
|
16
|
+
scope :by_os, ->(os) { where(os: os) }
|
17
|
+
scope :by_device, ->(device) { where(device: device) }
|
18
|
+
scope :by_country, ->(country) { where(country: country) }
|
19
|
+
scope :by_date_range, ->(start_date, end_date) { where(created_at: start_date..end_date) }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Umami
|
4
|
+
module Models
|
5
|
+
# Represents custom data associated with a session
|
6
|
+
class SessionData < Base
|
7
|
+
self.table_name = "session_data"
|
8
|
+
self.primary_key = "session_data_id"
|
9
|
+
belongs_to :website, foreign_key: "website_id"
|
10
|
+
belongs_to :session, foreign_key: "session_id"
|
11
|
+
scope :by_website, ->(website_id) { where(website_id: website_id) }
|
12
|
+
scope :by_session, ->(session_id) { where(session_id: session_id) }
|
13
|
+
scope :by_key, ->(key) { where(data_key: key) }
|
14
|
+
scope :by_distinct_id, ->(distinct_id) { where(distinct_id: distinct_id) }
|
15
|
+
scope :string_type, -> { where(data_type: 1) }
|
16
|
+
scope :number_type, -> { where(data_type: 2) }
|
17
|
+
scope :date_type, -> { where(data_type: 3) }
|
18
|
+
scope :by_date_range, ->(start_date, end_date) { where(created_at: start_date..end_date) }
|
19
|
+
def value
|
20
|
+
case data_type
|
21
|
+
when 1 then string_value
|
22
|
+
when 2 then number_value
|
23
|
+
when 3 then date_value
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Umami
|
4
|
+
module Models
|
5
|
+
# Represents a team for multi-tenancy
|
6
|
+
class Team < Base
|
7
|
+
self.table_name = "team"
|
8
|
+
self.primary_key = "team_id"
|
9
|
+
has_many :websites, foreign_key: "team_id"
|
10
|
+
has_many :team_users, class_name: "TeamUser", foreign_key: "team_id"
|
11
|
+
has_many :users, through: :team_users
|
12
|
+
scope :active, -> { where(deleted_at: nil) }
|
13
|
+
scope :by_access_code, ->(code) { where(access_code: code) }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Umami
|
4
|
+
module Models
|
5
|
+
# Represents team membership
|
6
|
+
class TeamUser < Base
|
7
|
+
self.table_name = "team_user"
|
8
|
+
self.primary_key = "team_user_id"
|
9
|
+
belongs_to :team, foreign_key: "team_id"
|
10
|
+
belongs_to :user, foreign_key: "user_id"
|
11
|
+
scope :by_role, ->(role) { where(role: role) }
|
12
|
+
scope :admins, -> { where(role: "admin") }
|
13
|
+
scope :members, -> { where(role: "member") }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Umami
|
4
|
+
module Models
|
5
|
+
# Represents a user in the Umami analytics system
|
6
|
+
class User < Base
|
7
|
+
self.table_name = "user"
|
8
|
+
self.primary_key = "user_id"
|
9
|
+
|
10
|
+
has_many :owned_websites, class_name: "Website", foreign_key: "user_id"
|
11
|
+
has_many :created_websites, class_name: "Website", foreign_key: "created_by"
|
12
|
+
has_many :team_users, class_name: "TeamUser", foreign_key: "user_id"
|
13
|
+
has_many :teams, through: :team_users
|
14
|
+
has_many :reports, foreign_key: "user_id"
|
15
|
+
|
16
|
+
scope :active, -> { where(deleted_at: nil) }
|
17
|
+
scope :with_role, ->(role) { where(role: role) }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Umami
|
4
|
+
module Models
|
5
|
+
# Represents a website being tracked
|
6
|
+
class Website < Base
|
7
|
+
self.table_name = "website"
|
8
|
+
self.primary_key = "website_id"
|
9
|
+
belongs_to :user, foreign_key: "user_id", optional: true
|
10
|
+
belongs_to :created_by_user, class_name: "User", foreign_key: "created_by", optional: true
|
11
|
+
belongs_to :team, foreign_key: "team_id", optional: true
|
12
|
+
has_many :event_data, class_name: "EventData", foreign_key: "website_id"
|
13
|
+
has_many :reports, foreign_key: "website_id"
|
14
|
+
has_many :session_data, class_name: "SessionData", foreign_key: "website_id"
|
15
|
+
scope :active, -> { where(deleted_at: nil) }
|
16
|
+
scope :by_user, ->(user_id) { where(user_id: user_id) }
|
17
|
+
scope :by_team, ->(team_id) { where(team_id: team_id) }
|
18
|
+
scope :public_shares, -> { where.not(share_id: nil) }
|
19
|
+
def sessions
|
20
|
+
Session.by_website(website_id)
|
21
|
+
end
|
22
|
+
|
23
|
+
def website_events
|
24
|
+
WebsiteEvent.by_website(website_id)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Umami
|
4
|
+
module Models
|
5
|
+
# Represents a page view or custom event
|
6
|
+
class WebsiteEvent < Base
|
7
|
+
self.table_name = "website_event"
|
8
|
+
self.primary_key = "event_id"
|
9
|
+
belongs_to :session, foreign_key: "session_id"
|
10
|
+
has_many :event_data, class_name: "EventData", foreign_key: "website_event_id"
|
11
|
+
scope :recent, -> { order(created_at: :desc) }
|
12
|
+
scope :by_website, ->(website_id) { where(website_id: website_id) }
|
13
|
+
scope :by_session, ->(session_id) { where(session_id: session_id) }
|
14
|
+
scope :by_visit, ->(visit_id) { where(visit_id: visit_id) }
|
15
|
+
scope :by_date_range, ->(start_date, end_date) { where(created_at: start_date..end_date) }
|
16
|
+
scope :page_views, -> { where(event_type: 1) }
|
17
|
+
scope :custom_events, -> { where(event_type: 2) }
|
18
|
+
scope :by_event_name, ->(name) { where(event_name: name) }
|
19
|
+
scope :by_url_path, ->(path) { where(url_path: path) }
|
20
|
+
scope :by_hostname, ->(hostname) { where(hostname: hostname) }
|
21
|
+
scope :with_utm_source, ->(source) { where(utm_source: source) }
|
22
|
+
scope :with_utm_campaign, ->(campaign) { where(utm_campaign: campaign) }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/umami/models.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "models/version"
|
4
|
+
require "active_record"
|
5
|
+
|
6
|
+
module Umami
|
7
|
+
# Provides read-only ActiveRecord models for accessing Umami Analytics data
|
8
|
+
module Models
|
9
|
+
class Error < StandardError; end
|
10
|
+
|
11
|
+
class << self
|
12
|
+
attr_accessor :table_prefix, :database
|
13
|
+
|
14
|
+
def configure
|
15
|
+
yield self
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
self.table_prefix = ""
|
20
|
+
self.database = nil
|
21
|
+
|
22
|
+
# Base class for all Umami models with read-only enforcement
|
23
|
+
class Base < ActiveRecord::Base
|
24
|
+
self.abstract_class = true
|
25
|
+
|
26
|
+
def self.inherited(subclass)
|
27
|
+
super
|
28
|
+
# Apply the database configuration when a model inherits from Base
|
29
|
+
return unless Umami::Models.database
|
30
|
+
|
31
|
+
subclass.connects_to database: Umami::Models.database
|
32
|
+
end
|
33
|
+
|
34
|
+
def readonly?
|
35
|
+
true
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.table_name
|
39
|
+
"#{Umami::Models.table_prefix}#{super}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
require_relative "models/user"
|
46
|
+
require_relative "models/session"
|
47
|
+
require_relative "models/website"
|
48
|
+
require_relative "models/website_event"
|
49
|
+
require_relative "models/event_data"
|
50
|
+
require_relative "models/session_data"
|
51
|
+
require_relative "models/team"
|
52
|
+
require_relative "models/team_user"
|
53
|
+
require_relative "models/report"
|
metadata
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: umami-read-models
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Andreas Zeitler
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2025-07-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '6.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '6.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: pg
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.0'
|
41
|
+
description: Provides read-only ActiveRecord models for accessing Umami Analytics
|
42
|
+
data directly from Rails applications
|
43
|
+
email:
|
44
|
+
- me@azeitler.com
|
45
|
+
executables: []
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- ".rubocop.yml"
|
50
|
+
- CHANGELOG.md
|
51
|
+
- LICENSE.txt
|
52
|
+
- README.md
|
53
|
+
- Rakefile
|
54
|
+
- lib/umami/models.rb
|
55
|
+
- lib/umami/models/event_data.rb
|
56
|
+
- lib/umami/models/report.rb
|
57
|
+
- lib/umami/models/session.rb
|
58
|
+
- lib/umami/models/session_data.rb
|
59
|
+
- lib/umami/models/team.rb
|
60
|
+
- lib/umami/models/team_user.rb
|
61
|
+
- lib/umami/models/user.rb
|
62
|
+
- lib/umami/models/version.rb
|
63
|
+
- lib/umami/models/website.rb
|
64
|
+
- lib/umami/models/website_event.rb
|
65
|
+
- sig/umami/models.rbs
|
66
|
+
homepage: https://github.com/azeitler/umami-read-models
|
67
|
+
licenses: []
|
68
|
+
metadata:
|
69
|
+
homepage_uri: https://github.com/azeitler/umami-read-models
|
70
|
+
source_code_uri: https://github.com/azeitler/umami-read-models
|
71
|
+
changelog_uri: https://github.com/azeitler/umami-read-models/blob/main/CHANGELOG.md
|
72
|
+
rubygems_mfa_required: 'true'
|
73
|
+
post_install_message:
|
74
|
+
rdoc_options: []
|
75
|
+
require_paths:
|
76
|
+
- lib
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: 3.0.0
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
requirements: []
|
88
|
+
rubygems_version: 3.4.19
|
89
|
+
signing_key:
|
90
|
+
specification_version: 4
|
91
|
+
summary: Read-only ActiveRecord models for Umami Analytics database
|
92
|
+
test_files: []
|