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 +4 -4
- data/README.md +27 -0
- data/app/controllers/twinkle/appcast_controller.rb +1 -1
- data/app/models/concerns/summarize.rb +2 -2
- data/app/models/twinkle/app.rb +1 -14
- data/app/models/twinkle/summary.rb +5 -2
- data/app/models/twinkle/version.rb +33 -12
- data/app/views/twinkle/appcast/show.xml.erb +42 -2
- data/db/migrate/20240517232121_create_twinkle_versions.rb +1 -1
- data/db/migrate/20240517234016_create_twinkle_summaries.rb +1 -1
- data/db/migrate/20240530023848_add_published_to_twinkle_versions.rb +6 -0
- data/db/migrate/20240626232135_add_fields_to_twinkle_versions.rb +19 -0
- data/lib/twinkle/concerns/models/app.rb +24 -0
- data/lib/twinkle/version.rb +1 -1
- data/lib/twinkle.rb +1 -0
- metadata +7 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 745ebe3beb30463d26dc3801ec74938b03ae089d267586d90ff736872a5bcbca
|
|
4
|
+
data.tar.gz: 678c0fadd35d1798fd7f1f69fc1ba9d170f7f12c3b3099311d96a1fd56f7bae8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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.
|
|
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
|
data/app/models/twinkle/app.rb
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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.
|
|
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.
|
|
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>
|
|
@@ -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
|
data/lib/twinkle/version.rb
CHANGED
data/lib/twinkle.rb
CHANGED
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
|
|
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.
|
|
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
|