plausible_api 0.3 → 0.4

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: 3164447cd3eac3c7a7cbf0c7e7cccd36eaf2bd195966283fc0da4ad023b7b21e
4
- data.tar.gz: 34acbb3e21f80bd3537d4273b6555dea0da7e4fdb5cf3dcbf8bede5d477e8ce0
3
+ metadata.gz: ee32c7197a3fd13ad0d1f4b4e0cb5571f9c98bd010ecf17e25943900c11f4616
4
+ data.tar.gz: 4f04483bf2726ad0ef5150d0660428f2475858b96ee2963cbc0bb48170a2b883
5
5
  SHA512:
6
- metadata.gz: 03273d140652347b6b282d94ff0c6294746fc7bd410dcaa1feab54a627d80ed64fa8eb1c9c56c0a3a0e3e2a95cb9cda755501987f46a3b795cb9dc2862aa1e01
7
- data.tar.gz: b6e84673b3763918a28488c717e3be089107571ecb1b357b4d7e2b1ded589b68eade9d65f615e70a6655b4571a73c7bf767a0af3363b6ca90631cd0810ea91e8
6
+ metadata.gz: 00f08349d212a36a721fff764dc6866268deaf6e088293f0f7700e9b02deeb0fa0babf319630c237eee5b422b4661618a3f7683c97fc32d733e5d3c5c208d48d
7
+ data.tar.gz: d6698061cf63da01aafd53ab0c9442991da22fc4550b424d57c19b1eef3669b6134b1b7fc74f103423f699428eba79d788af45715e8f577720716581cb5b33c5
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- plausible_api (0.3)
4
+ plausible_api (0.4)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -7,15 +7,38 @@ Add this gem to your Gemfile:
7
7
  ```rb
8
8
  gem 'plausible_api'
9
9
  ```
10
- Then you need to initialize a Client with your `site_id` (the domain) and your `token`.
10
+ Then you need to initialize a Client with your `site_id` (the domain) and your `api_key`.
11
+ Optionally, you can pass a third parameter in case you are using a self-hosted instance of Plausible (You don't need to add this third parameter if your are using the comercial version of Plausible).
11
12
  ```rb
12
- c = PlausibleApi::Client.new('dailytics.com', '123123')
13
+ # Using the comercial version:
14
+ c = PlausibleApi::Client.new("mysite.com", "MYAPIKEY")
15
+
16
+ # Using a self hosted instance
17
+ c = PlausibleApi::Client.new("mysite.com", "MYAPIKEY", "https://my-hosted-plausible.com")
13
18
 
14
19
  # Test if the site and token are valid
15
20
  c.valid?
16
21
  => true
17
22
  ```
18
23
 
24
+ If you will always work with the same site, you can set some (or all) of these 3 parameters
25
+ before initializing the client. On a Ruby on Rails app, you can add this to an initializer like
26
+ `config/initializers/plausible.rb`
27
+
28
+ ```rb
29
+ # Do not include a trailing slash
30
+ PlausibleApi.configure do |config|
31
+ config.base_url = "https://your-plausible-instance.com"
32
+ config.site_id = "dailytics.com"
33
+ config.api_key = "123123"
34
+ end
35
+ ```
36
+
37
+ And then, initializing the client simply like this:
38
+ ```rb
39
+ c = PlausibleApi::Client.new
40
+ ```
41
+
19
42
  ### Stats > Aggregate
20
43
 
21
44
  You have all these options to get the aggregate stats
@@ -87,18 +110,6 @@ c.event({
87
110
  })
88
111
  ```
89
112
 
90
-
91
- ### Self-hosted Plausible instances
92
-
93
- If you are using a self-hosted Plausible instance, you can set the `base_url` before initializing the client. On a Ruby on Rails app, you can add this to an initializer like `config/initializers/plausible.rb`
94
-
95
- ```rb
96
- # Do not include a trailing slash
97
- PlausibleApi.configure do |config|
98
- config.base_url = "https://your-plausible-instance.com"
99
- end
100
- ```
101
-
102
113
  ## Development
103
114
 
104
115
  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.
@@ -0,0 +1,41 @@
1
+ module PlausibleApi
2
+ class ApiBase < Utils
3
+ def request_class
4
+ # Net::HTTP::Post
5
+ raise NotImplementedError
6
+ end
7
+
8
+ def request_path
9
+ # "/api/event"
10
+ raise NotImplementedError
11
+ end
12
+
13
+ def request_auth?
14
+ true
15
+ end
16
+
17
+ def request_body
18
+ nil
19
+ end
20
+
21
+ def request_body?
22
+ present?(request_body)
23
+ end
24
+
25
+ def request_headers
26
+ {"content-type" => "application/json"}
27
+ end
28
+
29
+ def parse_response(body)
30
+ raise NotImplementedError
31
+ end
32
+
33
+ def errors
34
+ raise NotImplementedError
35
+ end
36
+
37
+ def valid?
38
+ errors.empty?
39
+ end
40
+ end
41
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "plausible_api/utils"
4
+ require "plausible_api/api_base"
3
5
  require "plausible_api/stats/base"
4
6
  require "plausible_api/stats/realtime/visitors"
5
7
  require "plausible_api/stats/aggregate"
@@ -14,10 +16,15 @@ require "uri"
14
16
  require "cgi"
15
17
 
16
18
  module PlausibleApi
17
- class Client
18
- def initialize(site_id, token)
19
- @site_id = site_id.to_s
20
- @token = token.to_s
19
+ class Client < Utils
20
+
21
+ attr_accessor :configuration
22
+
23
+ def initialize(site_id = nil, api_key = nil, base_url = nil)
24
+ @configuration = Configuration.new
25
+ @configuration.api_key = presence(api_key) || PlausibleApi.configuration.api_key
26
+ @configuration.site_id = presence(site_id) || PlausibleApi.configuration.site_id
27
+ @configuration.base_url = presence(base_url) || PlausibleApi.configuration.base_url
21
28
  end
22
29
 
23
30
  def aggregate(options = {})
@@ -53,14 +60,14 @@ module PlausibleApi
53
60
 
54
61
  def call(api)
55
62
  raise Error, api.errors unless api.valid?
56
- raise ConfigurationError, PlausibleApi.configuration.errors unless PlausibleApi.configuration.valid?
63
+ raise ConfigurationError, configuration.errors unless configuration.valid?
57
64
 
58
- url = "#{PlausibleApi.configuration.base_url}#{api.request_url.gsub("$SITE_ID", @site_id)}"
65
+ url = "#{configuration.base_url}#{api.request_path.gsub("$SITE_ID", configuration.site_id)}"
59
66
  uri = URI.parse(url)
60
67
 
61
68
  req = api.request_class.new(uri.request_uri)
62
69
  req.initialize_http_header(api.request_headers)
63
- req.add_field("authorization", "Bearer #{@token}") if api.request_auth?
70
+ req.add_field("authorization", "Bearer #{configuration.api_key}") if api.request_auth?
64
71
  req.body = api.request_body if api.request_body?
65
72
 
66
73
  http = Net::HTTP.new(uri.host, uri.port)
@@ -71,7 +78,7 @@ module PlausibleApi
71
78
  if SUCCESS_CODES.include?(response.code)
72
79
  api.parse_response response.body
73
80
  else
74
- raise StandardError.new response.body
81
+ raise Error.new response
75
82
  end
76
83
  end
77
84
  end
@@ -1,10 +1,13 @@
1
1
  module PlausibleApi
2
2
  class Configuration
3
- attr_accessor :base_url
3
+ attr_accessor :base_url, :default_user_agent, :api_key, :site_id
4
4
 
5
5
  # Setting up default values
6
6
  def initialize
7
7
  @base_url = "https://plausible.io"
8
+ @default_user_agent = "plausible_api_ruby/#{PlausibleApi::VERSION}"
9
+ @api_key = nil
10
+ @site_id = nil
8
11
  end
9
12
 
10
13
  def valid?
@@ -20,6 +23,12 @@ module PlausibleApi
20
23
  elsif base_url.end_with?("/")
21
24
  errors.push(base_url: "base_url should not end with a trailing slash")
22
25
  end
26
+ if api_key.nil? || api_key.empty?
27
+ errors.push(api_key: "api_key is required")
28
+ end
29
+ if site_id.nil? || site_id.empty?
30
+ errors.push(site_id: "site_id is required")
31
+ end
23
32
  errors
24
33
  end
25
34
  end
@@ -1,31 +1,17 @@
1
1
  module PlausibleApi
2
2
  module Event
3
- class Base
4
- DEFAULT_USER_AGENT = "plausible_api_ruby/#{PlausibleApi::VERSION}"
5
-
3
+ class Base < ApiBase
6
4
  def request_class
7
5
  Net::HTTP::Post
8
6
  end
9
7
 
10
- def request_url_base
8
+ def request_path
11
9
  "/api/event"
12
10
  end
13
11
 
14
- def request_url
15
- request_url_base
16
- end
17
-
18
12
  def request_auth?
19
13
  false
20
14
  end
21
-
22
- def request_body?
23
- true
24
- end
25
-
26
- def valid?
27
- errors.empty?
28
- end
29
15
  end
30
16
  end
31
17
  end
@@ -3,7 +3,6 @@
3
3
  module PlausibleApi
4
4
  module Event
5
5
  class Post < Base
6
- DEFAULT_USER_AGENT = "plausible_api_ruby/#{PlausibleApi::VERSION}"
7
6
  VALID_REVENUE_KEYS = %i[amount currency].freeze
8
7
  OPTIONS_IN_HEADERS = %i[ip user_agent].freeze
9
8
 
@@ -16,7 +15,7 @@ module PlausibleApi
16
15
 
17
16
  @domain = @options[:domain]
18
17
  @ip = @options[:ip]
19
- @user_agent = presence(@options[:user_agent]) || DEFAULT_USER_AGENT
18
+ @user_agent = presence(@options[:user_agent]) || PlausibleApi.configuration.default_user_agent
20
19
  @name = presence(@options[:name]) || "pageview"
21
20
  @url = presence(@options[:url]) || "app://localhost/#{@name}"
22
21
  @referrer = @options[:referrer]
@@ -60,10 +59,10 @@ module PlausibleApi
60
59
 
61
60
  if present?(@revenue)
62
61
  if @revenue.is_a?(Hash)
63
- unless valid_revenue_keys?(@revenue)
62
+ unless @revenue.keys.map(&:to_sym).all? { |key| VALID_REVENUE_KEYS.include?(key) }
64
63
  errors.push(
65
64
  revenue: "revenue must have keys #{VALID_REVENUE_KEYS.join(", ")} " \
66
- "but was #{@revenue.inspect}"
65
+ "but was #{@revenue.inspect}"
67
66
  )
68
67
  end
69
68
  else
@@ -83,19 +82,6 @@ module PlausibleApi
83
82
  def valid_revenue_keys?(revenue)
84
83
  revenue.keys.sort.map(&:to_sym) == VALID_REVENUE_KEYS.sort
85
84
  end
86
-
87
- def present?(value)
88
- !value.nil? && !value.empty?
89
- end
90
-
91
- def blank?(value)
92
- !present?(value)
93
- end
94
-
95
- def presence(value)
96
- return nil if blank?(value)
97
- value
98
- end
99
85
  end
100
86
  end
101
87
  end
@@ -3,20 +3,15 @@
3
3
  module PlausibleApi
4
4
  module Stats
5
5
  class Aggregate < Base
6
-
7
6
  def initialize(options = {})
8
- super({ period: '30d',
9
- metrics: 'visitors,visits,pageviews,views_per_visit,bounce_rate,visit_duration,events' }
10
- .merge(options))
7
+ super({period: "30d",
8
+ metrics: "visitors,visits,pageviews,views_per_visit,bounce_rate,visit_duration,events"}
9
+ .merge(options))
11
10
  end
12
11
 
13
- def request_url_base
12
+ def request_path_base
14
13
  "/api/v1/stats/aggregate?site_id=$SITE_ID"
15
14
  end
16
-
17
- def parse_response(body)
18
- JSON.parse(body)['results']
19
- end
20
15
  end
21
16
  end
22
17
  end
@@ -2,100 +2,79 @@
2
2
 
3
3
  module PlausibleApi
4
4
  module Stats
5
- class Base
5
+ class Base < ApiBase
6
+ ALLOWED_PERIODS = %w[12mo 6mo month 30d 7d day custom]
7
+ ALLOWED_METRICS = %w[visitors visits pageviews views_per_visit bounce_rate visit_duration events]
8
+ ALLOWED_COMPARE = %w[previous_period]
9
+ ALLOWED_INTERVALS = %w[date month]
10
+ ALLOWED_PROPERTIES = %w[event:goal event:page visit:entry_page visit:exit_page visit:source
11
+ visit:referrer visit:utm_medium visit:utm_source visit:utm_campaign
12
+ visit:utm_content visit:utm_term visit:device visit:browser
13
+ visit:browser_version visit:os visit:os_version visit:country visit:region visit:city
14
+ event:props:.+]
15
+ ALLOWED_FILTER_OPERATORS = %w[== != \|]
6
16
 
7
17
  def initialize(options = {})
8
- @options = { compare: nil, date: nil, filters: nil, interval: nil,
9
- limit: nil, metrics: nil, page: nil, period: nil,
10
- property: nil }.merge(options)
11
- @options[:period] = 'custom' if @options[:date]
12
- end
13
-
14
- def request_url_base
15
- raise NotImplementedError
18
+ @options = {compare: nil, date: nil, filters: nil, interval: nil,
19
+ limit: nil, metrics: nil, page: nil, period: nil,
20
+ property: nil}.merge(options)
21
+ @options[:period] = "custom" if @options[:date]
16
22
  end
17
23
 
18
24
  def request_class
19
25
  Net::HTTP::Get
20
26
  end
21
27
 
22
- def request_body?
23
- false
24
- end
25
-
26
- def request_body
27
- nil
28
- end
29
-
30
- def request_url
31
- params = @options.select{ |_,v| !v.to_s.empty? }
32
- [request_url_base, URI.encode_www_form(params)].reject{|e| e.empty?}.join('&')
33
- end
34
-
35
- def request_headers
36
- {}
37
- end
38
-
39
- def request_auth?
40
- true
28
+ def request_path
29
+ params = @options.select { |_, v| !v.to_s.empty? }
30
+ [request_path_base, URI.encode_www_form(params)].reject { |e| e.empty? }.join("&")
41
31
  end
42
32
 
43
33
  def parse_response(body)
44
- raise NotImplementedError
45
- end
46
-
47
- def valid?
48
- errors.empty?
34
+ JSON.parse(body)["results"]
49
35
  end
50
36
 
51
37
  def errors
52
- allowed_period = %w(12mo 6mo month 30d 7d day custom)
53
- allowed_metrics = %w(visitors visits pageviews views_per_visit bounce_rate visit_duration events)
54
- allowed_compare = %w(previous_period)
55
- allowed_interval = %w(date month)
56
- allowed_property = %w(event:page visit:entry_page visit:exit_page visit:source visit:referrer
57
- visit:utm_medium visit:utm_source visit:utm_campaign visit:device visit:browser
58
- visit:browser_version visit:os visit:os_version visit:country)
59
- e = 'Not a valid parameter. Allowed parameters are: '
38
+ e = "Not a valid parameter. Allowed parameters are: "
60
39
 
61
40
  errors = []
62
41
  if @options[:period]
63
- errors.push({ period: "#{e}#{allowed_period.join(', ')}" }) unless allowed_period.include? @options[:period]
42
+ errors.push({period: "#{e}#{ALLOWED_PERIODS.join(", ")}"}) unless ALLOWED_PERIODS.include? @options[:period]
64
43
  end
65
44
  if @options[:metrics]
66
- metrics_array = @options[:metrics].split(',')
67
- errors.push({ metrics: "#{e}#{allowed_metrics.join(', ')}" }) unless metrics_array & allowed_metrics == metrics_array
45
+ metrics_array = @options[:metrics].split(",")
46
+ errors.push({metrics: "#{e}#{ALLOWED_METRICS.join(", ")}"}) unless metrics_array & ALLOWED_METRICS == metrics_array
68
47
  end
69
48
  if @options[:compare]
70
- errors.push({ compare: "#{e}#{allowed_compare.join(', ')}" }) unless allowed_compare.include? @options[:compare]
49
+ errors.push({compare: "#{e}#{ALLOWED_COMPARE.join(", ")}"}) unless ALLOWED_COMPARE.include? @options[:compare]
71
50
  end
72
51
  if @options[:interval]
73
- errors.push({ interval: "#{e}#{allowed_interval.join(', ')}" }) unless allowed_interval.include? @options[:interval]
52
+ errors.push({interval: "#{e}#{ALLOWED_INTERVALS.join(", ")}"}) unless ALLOWED_INTERVALS.include? @options[:interval]
74
53
  end
75
54
  if @options[:property]
76
- errors.push({ property: "#{e}#{allowed_property.join(', ')}" }) unless allowed_property.include? @options[:property]
55
+ unless @options[:property].match?(/^(#{ALLOWED_PROPERTIES.join('|')})$/)
56
+ errors.push({property: "#{e}#{ALLOWED_PROPERTIES.join(", ")}"}) unless ALLOWED_PROPERTIES.include? @options[:property]
57
+ end
77
58
  end
78
- if @options[:filters]
79
- filters_array = @options[:filters].to_s.split(';')
80
- filters_array.each do |f|
81
- parts = f.split("==")
82
- errors.push({ filters: "Unrecognized filter: #{f}" }) unless parts.length == 2
83
- errors.push({ filters: "Unknown metric for filter: #{parts[0]}" }) unless allowed_property.include? parts[0]
59
+ @options[:filters]&.split(";")&.each do |filter|
60
+ unless filter.match?(/^(#{ALLOWED_PROPERTIES.join('|')})(#{ALLOWED_FILTER_OPERATORS.join('|')})(.+)$/)
61
+ errors.push({filters: "Filter #{filter} is not valid"})
84
62
  end
85
63
  end
64
+
86
65
  if @options[:limit]
87
- errors.push({ limit: "Limit param must be a positive number" }) unless @options[:limit].is_a? Integer and @options[:limit] > 0
66
+ errors.push({limit: "Limit param must be a positive number"}) unless @options[:limit].to_i > 0
88
67
  end
89
68
  if @options[:page]
90
- errors.push({ page: "Page param must be a positive number" }) unless @options[:page].is_a? Integer and @options[:page] > 0
69
+ errors.push({page: "Page param must be a positive number"}) unless @options[:page].to_i > 0
91
70
  end
92
71
  if @options[:date]
93
- errors.push({ date: 'You must define the period parameter as custom' }) unless @options[:period] == 'custom'
72
+ errors.push({date: "You must define the period parameter as custom"}) unless @options[:period] == "custom"
94
73
  date_array = @options[:date].split(",")
95
- errors.push({ date: 'You must define start and end dates divided by comma' }) unless date_array.length == 2
96
- regex = /\d{4}\-\d{2}\-\d{2}/
97
- errors.push({ date: 'Wrong format for the start date' }) unless date_array[0] =~ regex
98
- errors.push({ date: 'Wrong format for the end date' }) unless date_array[1] =~ regex
74
+ errors.push({date: "You must define start and end dates divided by comma"}) unless date_array.length == 2
75
+ regex = /\d{4}-\d{2}-\d{2}/
76
+ errors.push({date: "Wrong format for the start date"}) unless date_array[0]&.match?(regex)
77
+ errors.push({date: "Wrong format for the end date"}) unless date_array[1]&.match?(regex)
99
78
  end
100
79
  errors
101
80
  end
@@ -3,18 +3,13 @@
3
3
  module PlausibleApi
4
4
  module Stats
5
5
  class Breakdown < Base
6
-
7
6
  def initialize(options = {})
8
- super({ period: '30d', property: 'event:page' }.merge(options))
7
+ super({period: "30d", property: "event:page"}.merge(options))
9
8
  end
10
9
 
11
- def request_url_base
10
+ def request_path_base
12
11
  "/api/v1/stats/breakdown?site_id=$SITE_ID"
13
12
  end
14
-
15
- def parse_response(body)
16
- JSON.parse(body)['results']
17
- end
18
13
  end
19
14
  end
20
15
  end
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module PlausibleApi
3
+ module PlausibleApi
4
4
  module Stats
5
5
  module Realtime
6
6
  class Visitors < PlausibleApi::Stats::Base
7
- def request_url_base
7
+ def request_path_base
8
8
  "/api/v1/stats/realtime/visitors?site_id=$SITE_ID"
9
9
  end
10
10
 
@@ -14,4 +14,4 @@ module PlausibleApi
14
14
  end
15
15
  end
16
16
  end
17
- end
17
+ end
@@ -3,18 +3,13 @@
3
3
  module PlausibleApi
4
4
  module Stats
5
5
  class Timeseries < Base
6
-
7
6
  def initialize(options = {})
8
- super({ period: '30d' }.merge(options))
7
+ super({period: "30d"}.merge(options))
9
8
  end
10
9
 
11
- def request_url_base
10
+ def request_path_base
12
11
  "/api/v1/stats/timeseries?site_id=$SITE_ID"
13
12
  end
14
-
15
- def parse_response(body)
16
- JSON.parse(body)['results']
17
- end
18
13
  end
19
14
  end
20
15
  end
@@ -0,0 +1,16 @@
1
+ module PlausibleApi
2
+ class Utils
3
+ def present?(value)
4
+ !value.nil? && !value.empty?
5
+ end
6
+
7
+ def blank?(value)
8
+ !present?(value)
9
+ end
10
+
11
+ def presence(value)
12
+ return false if blank?(value)
13
+ value
14
+ end
15
+ end
16
+ end
@@ -1,3 +1,3 @@
1
1
  module PlausibleApi
2
- VERSION = "0.3"
2
+ VERSION = "0.4"
3
3
  end
data/lib/plausible_api.rb CHANGED
@@ -7,7 +7,6 @@ module PlausibleApi
7
7
 
8
8
  class ConfigurationError < StandardError; end
9
9
 
10
- # Your code goes here...
11
10
  class << self
12
11
  def configuration
13
12
  @configuration ||= Configuration.new
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: plausible_api
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.3'
4
+ version: '0.4'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gustavo Garcia
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-02-02 00:00:00.000000000 Z
11
+ date: 2024-02-24 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A very humble wrapper for the new API by Plausible
14
14
  email:
@@ -29,6 +29,7 @@ files:
29
29
  - examples/basic.rb
30
30
  - examples/event.rb
31
31
  - lib/plausible_api.rb
32
+ - lib/plausible_api/api_base.rb
32
33
  - lib/plausible_api/client.rb
33
34
  - lib/plausible_api/configuration.rb
34
35
  - lib/plausible_api/event/base.rb
@@ -38,6 +39,7 @@ files:
38
39
  - lib/plausible_api/stats/breakdown.rb
39
40
  - lib/plausible_api/stats/realtime/visitors.rb
40
41
  - lib/plausible_api/stats/timeseries.rb
42
+ - lib/plausible_api/utils.rb
41
43
  - lib/plausible_api/version.rb
42
44
  - plausible_api.gemspec
43
45
  homepage: https://github.com/dailytics/plausible_api