twinkle 0.1.0 → 0.2.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: a8ac554f1dff3ef39f4915f981ec076b50702272d4e580fd53c883065b01c430
4
- data.tar.gz: dc5f679b7366c31e316f067886298f5408b32d11706bdd56de76a95ad6550685
3
+ metadata.gz: 745ebe3beb30463d26dc3801ec74938b03ae089d267586d90ff736872a5bcbca
4
+ data.tar.gz: 678c0fadd35d1798fd7f1f69fc1ba9d170f7f12c3b3099311d96a1fd56f7bae8
5
5
  SHA512:
6
- metadata.gz: 37eaf8576e4d1b47332eb5fd48c082cb08b75df2578bba102308d5d74e550e3964bbae168a462297e4af2b78f9f78d5a9f0cbe1c2041cc9236f38b13d8ebeaa2
7
- data.tar.gz: 9fcf89b14a7a6b945fe607af393db115f79d28b29a4d33585844b82009455103871060dffcc41e0017ef597ef6a05a92091410fd012fc30964fb7d1156a25181
6
+ metadata.gz: a80e569a9be8977f3447583de2e030ccf45341a25bc60a845b02766be74b7dadf19f4aa5314d6dcdd2f935d44efadf2c668ea5f43c55cc0ce7c10115bd3d14d3
7
+ data.tar.gz: a11ac6363d06444ef8aa07ca5173e8c1770ce44295ab89ca32505c0140565244b2a118a2bdac1ccccf546cd04d9ac1e8dea0040c33649691846bbad606c4de1c
data/README.md CHANGED
@@ -46,10 +46,37 @@ mount Twinkle::Engine => "/"
46
46
 
47
47
  This will mount the appcast routes at /updates/:app.slug
48
48
 
49
+ ## Extending Twinkle Apps
50
+
51
+ You can extend the Twinkle::App model by creating app/models/twinkle/app.rb
52
+
53
+ ```ruby
54
+ class Twinkle::App < ApplicationRecord
55
+ include Twinkle::Concerns::Models::App
56
+ include Summarize
57
+
58
+ # Your custom app code and validations etc go here
59
+ end
60
+ ```
61
+
49
62
  ## Contributing
50
63
 
51
64
  Pull requests welcome.
52
65
 
66
+ ## Testing
67
+
68
+ ```bash
69
+ bin/rails db:test:prepare
70
+ bin/test
71
+ ```
72
+
73
+ ## Building the gem
74
+
75
+ ```bash
76
+ gem build
77
+ gem push twinkle-x.x.x.gem
78
+ ```
79
+
53
80
  ## License
54
81
 
55
82
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -1,7 +1,7 @@
1
1
  module Twinkle
2
2
  class AppcastController < ApplicationController
3
3
  def show
4
- @app = App.with_versions.find_by!(slug: params[:slug])
4
+ @app = App.includes(:versions).find_by!(slug: params[:slug])
5
5
  Event.create(app: @app, **event_params)
6
6
  render layout: false, formats: :xml
7
7
  end
@@ -4,7 +4,7 @@ module Summarize
4
4
  included do
5
5
  def summarize_events(period, start_date, end_date)
6
6
  # Find or create a summary for the week
7
- summary = Twinkle::Summary.with_datapoints.find_or_create_by(app: self, period: period, start_date: start_date, end_date: end_date)
7
+ summary = Twinkle::Summary.find_or_create_by(app: self, period: period, start_date: start_date, end_date: end_date)
8
8
  data_hash = get_data_hash(start_date, end_date)
9
9
  datapoints = get_datapoints(summary, data_hash)
10
10
  save_summary(datapoints)
@@ -17,7 +17,7 @@ module Summarize
17
17
  events.created_between(start_date, end_date).find_each do |event|
18
18
  data_hash['users']['sessions'] = (data_hash.dig('users', 'sessions') || 0) + 1
19
19
  Twinkle::Event.fields.each do |field|
20
- data_hash[field][event[field]] = (data_hash.dig(field, event[field]) || 0) + 1
20
+ data_hash[field][event[field]] = (data_hash.dig(field, event[field]) || 0) + 1 if event[field].present?
21
21
  end
22
22
  end
23
23
  data_hash
@@ -1,19 +1,6 @@
1
1
  module Twinkle
2
2
  class App < ApplicationRecord
3
+ include Twinkle::Concerns::Models::App
3
4
  include Summarize
4
-
5
- has_many :versions, foreign_key: 'twinkle_app_id', class_name: 'Twinkle::Version'
6
- has_many :events, foreign_key: 'twinkle_app_id', class_name: 'Twinkle::Event'
7
- has_many :summaries, foreign_key: 'twinkle_app_id', class_name: 'Twinkle::Summary'
8
- has_one_attached :icon
9
-
10
- scope :with_versions, -> { includes(:versions).order('twinkle_versions.build desc') }
11
- scope :with_latest_version, -> { includes(:versions).order('twinkle_versions.build desc').limit(1) }
12
- scope :with_latest_summary, -> { includes(:summaries).order('twinkle_summaries.created_at desc').limit(1) }
13
- scope :with_summaries_since, ->(date) { includes(:summaries).where('twinkle_summaries.start >= ?', date).order('twinkle_summaries.start asc') }
14
-
15
- validates :name, presence: true
16
- validates :slug, presence: true, uniqueness: true
17
- validates :description, presence: true
18
5
  end
19
6
  end
@@ -1,10 +1,13 @@
1
1
  module Twinkle
2
2
  class Summary < ApplicationRecord
3
3
  belongs_to :app, foreign_key: :twinkle_app_id, class_name: 'Twinkle::App'
4
- has_many :datapoints, foreign_key: :twinkle_summary_id, class_name: 'Twinkle::Datapoint'
4
+ has_many :datapoints, foreign_key: :twinkle_summary_id, class_name: 'Twinkle::Datapoint', dependent: :delete_all
5
5
 
6
+ # Virtual relationships
7
+ has_one :latest_user_summary, -> { where(name: 'users').order('twinkle_datapoints.value desc').limit(1) }, foreign_key: :twinkle_summary_id, class_name: 'Twinkle::Datapoint'
8
+
9
+ scope :since, ->(date) { where('twinkle_summaries.start_date >= ?', date).order('twinkle_summaries.start_date asc') }
6
10
  scope :week_of, -> (start_date, end_date) { where(period: :week).where("start_date >= ? AND end_date <= ?", start_date, end_date) }
7
- scope :with_datapoints, -> { includes(:datapoints) }
8
11
 
9
12
  enum period: { week: 0, fortnight: 1, month: 2 }
10
13
 
@@ -5,27 +5,40 @@ module Twinkle
5
5
  belongs_to :app, foreign_key: 'twinkle_app_id', class_name: 'Twinkle::App'
6
6
 
7
7
  validates :number, presence: true
8
- validates :build, presence: true
9
- validates :description, presence: true
10
- validates :binary_url, presence: true
11
- validates :length, presence: true
12
- validates :length, numericality: { only_integer: true, greater_than: 0 }
8
+ validates :build, numericality: { only_integer: true, greater_than: 0 }
9
+ validates :description, presence: true, if: -> { published }
10
+ validates :binary_url, presence: true, if: -> { published }
11
+ validates :length, presence: true, if: -> { published }
12
+ validates :length, numericality: { only_integer: true, greater_than: 0 }, allow_blank: true
13
+ validates :phased_rollout_interval, numericality: { only_integer: true, greater_than: 0 }, allow_blank: true
13
14
 
14
15
  # validates that the binary_url is a valid URL
15
16
  validate :binary_url_is_url
16
17
 
18
+ # validates that the release_notes_link is a valid URL
19
+ validate :release_notes_link_is_url
20
+
21
+ # validates that the full_release_notes_link is a valid URL
22
+ validate :full_release_notes_link_is_url
23
+
17
24
  # validates that one of the two signatures is present
18
- validate :signature_present
25
+ validate :signature_present, if: -> { published }
19
26
 
20
27
  private
21
28
 
22
29
  def binary_url_is_url
23
- begin
24
- uri = URI.parse(binary_url)
25
- raise URI::InvalidURIError unless uri.is_a?(URI::HTTP) && uri.host.present?
26
- rescue URI::InvalidURIError
27
- errors.add(:binary_url, "is not a valid URL")
28
- end
30
+ return true if binary_url.blank?
31
+ errors.add(:binary_url, "is not a valid URL") unless is_url?(binary_url)
32
+ end
33
+
34
+ def release_notes_link_is_url
35
+ return true if release_notes_link.blank?
36
+ errors.add(:release_notes_link, "is not a valid URL") unless is_url?(release_notes_link)
37
+ end
38
+
39
+ def full_release_notes_link_is_url
40
+ return true if full_release_notes_link.blank?
41
+ errors.add(:full_release_notes_link, "is not a valid URL") unless is_url?(full_release_notes_link)
29
42
  end
30
43
 
31
44
  def signature_present
@@ -33,5 +46,13 @@ module Twinkle
33
46
  errors.add(:base, "At least one of the two signatures must be present")
34
47
  end
35
48
  end
49
+
50
+ private
51
+ def is_url?(url)
52
+ uri = URI.parse(url)
53
+ uri.is_a?(URI::HTTP) && uri.host.present?
54
+ rescue URI::InvalidURIError
55
+ false
56
+ end
36
57
  end
37
58
  end
@@ -5,14 +5,54 @@
5
5
  <% @app.versions.each do |version| %>
6
6
  <item>
7
7
  <title><%= version.number %></title>
8
- <pubDate><%= version.inserted_at %></pubDate>
8
+ <pubDate><%= version.published_at.strftime("%a, %d %b %Y %H:%M:%S %z") %></pubDate>
9
9
  <sparkle:version><%= version.build %></sparkle:version>
10
10
  <sparkle:shortVersionString><%= version.number %></sparkle:shortVersionString>
11
+ <% if version.link.present? %>
12
+ <link><%= version.link %></link>
13
+ <% end %>
14
+ <% if version.channel.present? %>
15
+ <sparkle:channel><%= version.channel %></sparkle:channel>
16
+ <% end %>
17
+ <% if version.release_notes_link.present? %>
18
+ <sparkle:releaseNotesLink><%= version.release_notes_link %></sparkle:releaseNotesLink>
19
+ <% end %>
20
+ <% if version.full_release_notes_link.present? %>
21
+ <sparkle:fullReleaseNotesLink><%= version.full_release_notes_link %></sparkle:fullReleaseNotesLink>
22
+ <% end %>
23
+ <% if version.min_system_version.present? %>
11
24
  <sparkle:minimumSystemVersion><%= version.min_system_version %></sparkle:minimumSystemVersion>
25
+ <% end %>
26
+ <% if version.max_system_version.present? %>
27
+ <sparkle:maximumSystemVersion><%= version.max_system_version %></sparkle:maximumSystemVersion>
28
+ <% end %>
29
+ <% if version.minimum_auto_update_version.present? %>
30
+ <sparkle:minimumAutoupdateVersion><%= version.minimum_auto_update_version %></sparkle:minimumAutoupdateVersion>
31
+ <% end %>
32
+ <% if version.ignore_skipped_upgrades_below_version.present? %>
33
+ <sparkle:ignoreSkippedUpgradesBelowVersion><%= version.ignore_skipped_upgrades_below_version %></sparkle:ignoreSkippedUpgradesBelowVersion>
34
+ <% end %>
35
+ <% if version.informational_update_below_version.present? %>
36
+ <sparkle:informationalUpdate>
37
+ <sparkle:belowVersion><%= version.informational_update_below_version %></sparkle:belowVersion>
38
+ </sparkle:informationalUpdate>
39
+ <% end %>
40
+ <% if version.critical %>
41
+ <% if version.sparkle_two %>
42
+ <sparkle:criticalUpdate<%= version.critical_version.present? ? raw(" sparkle:version=\"#{version.critical_version}\"") : "" %>></sparkle:criticalUpdate>
43
+ <% else %>
44
+ <sparkle:tags>
45
+ <sparkle:criticalUpdate></sparkle:criticalUpdate>
46
+ </sparkle:tags>
47
+ <% end %>
48
+ <% end %>
49
+ <% if version.phased_rollout_interval.present? %>
50
+ <sparkle:phasedRolloutInterval><%= version.phased_rollout_interval %></sparkle:phasedRolloutInterval>
51
+ <% end %>
12
52
  <description>
13
53
  <%= version.description %>
14
54
  </description>
15
- <enclosure url="<%= version.url %>" length="<%= version.length %>" type="application/octet-stream" sparkle:dsaSignature="<%= version.dsa_signature %>" sparkle:edSignature="<%= version.ed_signature %>"/>
55
+ <enclosure url="<%= version.binary_url %>" length="<%= version.length %>" type="application/octet-stream" sparkle:dsaSignature="<%= version.dsa_signature %>" sparkle:edSignature="<%= version.ed_signature %>"/>
16
56
  </item>
17
57
  <% end %>
18
58
  </channel>
@@ -12,6 +12,6 @@ class CreateTwinkleVersions < ActiveRecord::Migration[7.1]
12
12
 
13
13
  t.timestamps
14
14
  end
15
- add_index :twinkle_versions, :build
15
+ add_index :twinkle_versions, [:twinkle_app_id, :build], unique: true
16
16
  end
17
17
  end
@@ -8,6 +8,6 @@ class CreateTwinkleSummaries < ActiveRecord::Migration[7.1]
8
8
 
9
9
  t.timestamps
10
10
  end
11
- add_index :twinkle_summaries, [:period, :start_date, :end_date]
11
+ add_index :twinkle_summaries, [:period, :start_date, :end_date], unique: true
12
12
  end
13
13
  end
@@ -0,0 +1,6 @@
1
+ class AddPublishedToTwinkleVersions < ActiveRecord::Migration[7.1]
2
+ def change
3
+ add_column :twinkle_versions, :published, :boolean, default: false, null: false
4
+ add_index :twinkle_versions, [:twinkle_app_id, :published]
5
+ end
6
+ end
@@ -0,0 +1,19 @@
1
+ class AddFieldsToTwinkleVersions < ActiveRecord::Migration[7.1]
2
+ def change
3
+ add_column :twinkle_versions, :sparkle_two, :boolean, default: true
4
+ add_column :twinkle_versions, :link, :string
5
+ add_column :twinkle_versions, :min_system_version, :string
6
+ add_column :twinkle_versions, :max_system_version, :string
7
+ add_column :twinkle_versions, :minimum_auto_update_version, :string
8
+ add_column :twinkle_versions, :ignore_skipped_upgrades_below_version, :string
9
+ add_column :twinkle_versions, :informational_update_below_version, :string
10
+ add_column :twinkle_versions, :critical, :boolean, default: false
11
+ add_column :twinkle_versions, :critical_version, :string
12
+ add_column :twinkle_versions, :phased_rollout_interval, :integer
13
+ add_column :twinkle_versions, :channel, :string
14
+ add_column :twinkle_versions, :release_notes_link, :string
15
+ add_column :twinkle_versions, :full_release_notes_link, :string
16
+ add_column :twinkle_versions, :published_at, :datetime
17
+ change_column :twinkle_versions, :build, :integer, using: 'build::integer'
18
+ end
19
+ end
@@ -0,0 +1,24 @@
1
+ module Twinkle
2
+ module Concerns
3
+ module Models
4
+ module App
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ has_many :versions, -> { order('twinkle_versions.build desc') }, foreign_key: 'twinkle_app_id', class_name: 'Twinkle::Version', dependent: :destroy
9
+ has_many :events, foreign_key: 'twinkle_app_id', class_name: 'Twinkle::Event', dependent: :delete_all
10
+ has_many :summaries, foreign_key: 'twinkle_app_id', class_name: 'Twinkle::Summary', dependent: :destroy
11
+ has_one_attached :icon
12
+
13
+ # Virtual relationships
14
+ has_one :latest_version, -> { order('twinkle_versions.build desc').limit(1) }, foreign_key: 'twinkle_app_id', class_name: 'Twinkle::Version'
15
+ has_one :latest_weekly_summary, -> { where(period: :week).order('twinkle_summaries.start_date desc').limit(1) }, foreign_key: 'twinkle_app_id', class_name: 'Twinkle::Summary'
16
+
17
+ validates :name, presence: true
18
+ validates :slug, presence: true, uniqueness: true
19
+ validates :description, presence: true
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,3 +1,3 @@
1
1
  module Twinkle
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.1"
3
3
  end
data/lib/twinkle.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require "twinkle/version"
2
2
  require "twinkle/engine"
3
+ require "twinkle/concerns/models/app"
3
4
 
4
5
  module Twinkle
5
6
  # Your code goes here...
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: twinkle
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tim Marks
@@ -57,12 +57,16 @@ files:
57
57
  - db/migrate/20240517233457_create_twinkle_events.rb
58
58
  - db/migrate/20240517234016_create_twinkle_summaries.rb
59
59
  - db/migrate/20240518000405_create_twinkle_datapoints.rb
60
+ - db/migrate/20240530023848_add_published_to_twinkle_versions.rb
61
+ - db/migrate/20240626232135_add_fields_to_twinkle_versions.rb
60
62
  - lib/tasks/twinkle_tasks.rake
61
63
  - lib/twinkle.rb
64
+ - lib/twinkle/concerns/models/app.rb
62
65
  - lib/twinkle/engine.rb
63
66
  - lib/twinkle/version.rb
64
67
  homepage: https://github.com/imothee/twinkle
65
- licenses: []
68
+ licenses:
69
+ - MIT
66
70
  metadata:
67
71
  allowed_push_host: https://rubygems.org/
68
72
  homepage_uri: https://github.com/imothee/twinkle
@@ -83,7 +87,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
83
87
  - !ruby/object:Gem::Version
84
88
  version: '0'
85
89
  requirements: []
86
- rubygems_version: 3.3.26
90
+ rubygems_version: 3.5.9
87
91
  signing_key:
88
92
  specification_version: 4
89
93
  summary: Twinkle is a Rails engine for hosting appcast and collecting anonymous sparkle-project