app_status_notification 0.9.0.beta1

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.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rubocop.yml +69 -0
  4. data/Gemfile +18 -0
  5. data/Gemfile.lock +112 -0
  6. data/LICENSE +21 -0
  7. data/README.md +22 -0
  8. data/app_status_notification.gemspec +46 -0
  9. data/config/locales/en.yml +10 -0
  10. data/config/locales/zh.yml +54 -0
  11. data/config/notification.yml +30 -0
  12. data/exe/app_status_notification +5 -0
  13. data/lib/app_status_notification.rb +24 -0
  14. data/lib/app_status_notification/command.rb +64 -0
  15. data/lib/app_status_notification/config.rb +234 -0
  16. data/lib/app_status_notification/connect_api.rb +92 -0
  17. data/lib/app_status_notification/connect_api/auth.rb +44 -0
  18. data/lib/app_status_notification/connect_api/clients/app.rb +46 -0
  19. data/lib/app_status_notification/connect_api/clients/app_store_version.rb +28 -0
  20. data/lib/app_status_notification/connect_api/clients/build.rb +29 -0
  21. data/lib/app_status_notification/connect_api/model.rb +152 -0
  22. data/lib/app_status_notification/connect_api/models/app.rb +27 -0
  23. data/lib/app_status_notification/connect_api/models/app_store_version.rb +84 -0
  24. data/lib/app_status_notification/connect_api/models/app_store_version_submission.rb +15 -0
  25. data/lib/app_status_notification/connect_api/models/build.rb +30 -0
  26. data/lib/app_status_notification/connect_api/models/pre_release_version.rb +16 -0
  27. data/lib/app_status_notification/connect_api/response.rb +102 -0
  28. data/lib/app_status_notification/error.rb +38 -0
  29. data/lib/app_status_notification/helper.rb +16 -0
  30. data/lib/app_status_notification/notification.rb +57 -0
  31. data/lib/app_status_notification/notifications/adapter.rb +25 -0
  32. data/lib/app_status_notification/notifications/dingtalk.rb +59 -0
  33. data/lib/app_status_notification/notifications/slack.rb +62 -0
  34. data/lib/app_status_notification/notifications/wecom.rb +48 -0
  35. data/lib/app_status_notification/runner.rb +409 -0
  36. data/lib/app_status_notification/store.rb +68 -0
  37. data/lib/app_status_notification/version.rb +5 -0
  38. data/lib/app_status_notification/watchman.rb +42 -0
  39. metadata +283 -0
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AppStatusNotification
4
+ class ConnectAPI
5
+ module Client
6
+ module AppStoreVersion
7
+ def versions(query = {})
8
+ get('appStoreVersions', query)
9
+ end
10
+
11
+ def version(id, query = {})
12
+ get("appStoreVersions/#{id}", query)
13
+ end
14
+
15
+ def select_version_build(id, build_id:)
16
+ body = {
17
+ data: {
18
+ type: 'builds',
19
+ id: build_id
20
+ }
21
+ }
22
+
23
+ patch("appStoreVersions/#{id}/relationships/build", body: body)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AppStatusNotification
4
+ class ConnectAPI
5
+ module Client
6
+ module Build
7
+ def app_latest_build(id)
8
+ app_builds(id, limit: 1).to_model
9
+ end
10
+
11
+ def app_builds(id, **kargs)
12
+ kargs = kargs.merge(filter: { app: id })
13
+ builds(**kargs)
14
+ end
15
+
16
+ def builds(limit: 200, sort: '-uploadedDate',
17
+ includes: ConnectAPI::Model::Build::ESSENTIAL_INCLUDES,
18
+ **kargs)
19
+
20
+ kargs = kargs.merge(limit: limit)
21
+ .merge(sort: sort)
22
+ .merge(include: includes)
23
+
24
+ get('builds', **kargs)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+
5
+ module AppStatusNotification
6
+ class ConnectAPI
7
+ module Platform
8
+ IOS = 'IOS'
9
+ MAC_OS = 'MAC_OS'
10
+ TV_OS = 'TV_OS'
11
+ WATCH_OS = 'WATCH_OS'
12
+
13
+ ALL = [IOS, MAC_OS, TV_OS, WATCH_OS]
14
+ end
15
+
16
+ module ProcessStatus
17
+ PROCESSING = 'PROCESSING'
18
+ FAILED = 'FAILED'
19
+ INVALID = 'INVALID'
20
+ VALID = 'VALID'
21
+ end
22
+
23
+ module Model
24
+ def self.included(base)
25
+ Parser.types ||= []
26
+ Parser.types << base
27
+ base.extend(Parser)
28
+ end
29
+
30
+ attr_accessor :id
31
+ attr_reader :attributes
32
+ attr_reader :rate
33
+
34
+ def initialize(id, attributes, rate)
35
+ @id = id
36
+ @attributes = attributes
37
+ @rate = rate
38
+
39
+ update_attributes(attributes)
40
+ end
41
+
42
+ def update_attributes(attributes)
43
+ (attributes || []).each do |key, value|
44
+
45
+ method = "#{key.to_s.underscore}=".to_sym
46
+ self.send(method, value) if self.respond_to?(method)
47
+ end
48
+ end
49
+ end
50
+
51
+ module Parser
52
+ class << self
53
+ attr_accessor :types
54
+ attr_accessor :types_cache
55
+ end
56
+
57
+ def self.parse(response, rate)
58
+ body = response.body
59
+ data = body['data']
60
+ raise ConnectAPIError, 'No data' unless data
61
+
62
+ included = body['included'] || []
63
+ if data.kind_of?(Hash)
64
+ inflate_model(data, included, rate: rate)
65
+ elsif data.kind_of?(Array)
66
+ return data.map do |model_data|
67
+ inflate_model(model_data, included, rate: rate)
68
+ end
69
+ else
70
+ raise ConnectAPIError, "'data' is neither a hash nor an array"
71
+ end
72
+ end
73
+
74
+ def self.inflate_model(model_data, included, rate: {})
75
+ # Find class
76
+ type_class = find_class(model_data)
77
+ raise "No type class found for #{model_data['type']}" unless type_class
78
+
79
+ # Get id and attributes needed for inflating
80
+ id = model_data['id']
81
+ attributes = model_data['attributes']
82
+
83
+ # Instantiate object and inflate relationships
84
+ relationships = model_data['relationships'] || []
85
+ type_instance = type_class.new(id, attributes, rate)
86
+
87
+ inflate_model_relationships(type_instance, relationships, included)
88
+ end
89
+
90
+ def self.find_class(model_data)
91
+ # Initialize cache
92
+ @types_cache ||= {}
93
+
94
+ # Find class in cache
95
+ type_string = model_data['type']
96
+ type_class = @types_cache[type_string]
97
+ return type_class if type_class
98
+
99
+ # Find class in array
100
+ type_class = @types.find do |type|
101
+ type.type == type_string
102
+ end
103
+
104
+ # Cache and return class
105
+ type_class
106
+ end
107
+
108
+ def self.inflate_model_relationships(type_instance, relationships, included)
109
+ # Relationship attributes to set
110
+ attributes = {}
111
+
112
+ # 1. Iterate over relationships
113
+ # 2. Find id and type
114
+ # 3. Find matching id and type in included
115
+ # 4. Inflate matching data and set in attributes
116
+ relationships.each do |key, value|
117
+ # Validate data exists
118
+ value_data_or_datas = value['data']
119
+ next unless value_data_or_datas
120
+
121
+ # Map an included data object
122
+ map_data = lambda do |value_data|
123
+ id = value_data['id']
124
+ type = value_data['type']
125
+
126
+ relationship_data = included.find do |included_data|
127
+ id == included_data['id'] && type == included_data['type']
128
+ end
129
+
130
+ inflate_model(relationship_data, included) if relationship_data
131
+ end
132
+
133
+ # Map a hash or an array of data
134
+ if value_data_or_datas.kind_of?(Hash)
135
+ attributes[key] = map_data.call(value_data_or_datas)
136
+ elsif value_data_or_datas.kind_of?(Array)
137
+ attributes[key] = value_data_or_datas.map(&map_data)
138
+ end
139
+ end
140
+
141
+ type_instance.update_attributes(attributes)
142
+ type_instance
143
+ end
144
+ end
145
+ end
146
+ end
147
+
148
+ require 'app_status_notification/connect_api/models/app'
149
+ require 'app_status_notification/connect_api/models/app_store_version'
150
+ require 'app_status_notification/connect_api/models/build'
151
+ require 'app_status_notification/connect_api/models/pre_release_version'
152
+ require 'app_status_notification/connect_api/models/app_store_version_submission'
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'app_status_notification/connect_api/model'
4
+
5
+ module AppStatusNotification::ConnectAPI::Model
6
+ class App
7
+ include AppStatusNotification::ConnectAPI::Model
8
+
9
+ attr_accessor :name
10
+ attr_accessor :bundle_id
11
+ attr_accessor :sku
12
+ attr_accessor :primary_locale
13
+ attr_accessor :is_opted_in_to_distribute_ios_app_on_mac_app_store
14
+ attr_accessor :removed
15
+ attr_accessor :is_aag
16
+ attr_accessor :available_in_new_territories
17
+ attr_accessor :content_rights_declaration
18
+
19
+ # include
20
+ attr_accessor :app_store_versions
21
+ attr_accessor :builds
22
+
23
+ def self.type
24
+ 'apps'
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'app_status_notification/connect_api/model'
4
+
5
+ module AppStatusNotification::ConnectAPI::Model
6
+ class AppStoreVersion
7
+ include AppStatusNotification::ConnectAPI::Model
8
+
9
+ attr_accessor :platform
10
+ attr_accessor :version_string
11
+ attr_accessor :app_store_state
12
+ attr_accessor :release_type
13
+ attr_accessor :earliest_release_date # 2020-06-17T12:00:00-07:00
14
+ attr_accessor :uses_idfa
15
+ attr_accessor :downloadable
16
+ attr_accessor :created_date
17
+ attr_accessor :version
18
+ attr_accessor :uploaded_date
19
+ attr_accessor :expiration_date
20
+ attr_accessor :expired
21
+ attr_accessor :store_icon
22
+ attr_accessor :watch_store_icon
23
+ attr_accessor :copyright
24
+ attr_accessor :min_os_version
25
+
26
+ # include
27
+ attr_accessor :app
28
+ attr_accessor :app_store_version_submission
29
+ attr_accessor :build
30
+
31
+ ESSENTIAL_INCLUDES = [
32
+ 'app',
33
+ 'appStoreVersionSubmission',
34
+ 'build'
35
+ ].join(',')
36
+
37
+ module AppStoreState
38
+ READY_FOR_SALE = 'READY_FOR_SALE'
39
+ PROCESSING_FOR_APP_STORE = 'PROCESSING_FOR_APP_STORE'
40
+ PENDING_DEVELOPER_RELEASE = 'PENDING_DEVELOPER_RELEASE'
41
+ IN_REVIEW = 'IN_REVIEW'
42
+ WAITING_FOR_REVIEW = 'WAITING_FOR_REVIEW'
43
+ DEVELOPER_REJECTED = 'DEVELOPER_REJECTED'
44
+ REJECTED = 'REJECTED'
45
+ PREPARE_FOR_SUBMISSION = 'PREPARE_FOR_SUBMISSION'
46
+ METADATA_REJECTED = 'METADATA_REJECTED'
47
+ INVALID_BINARY = 'INVALID_BINARY'
48
+ end
49
+
50
+ module ReleaseType
51
+ AFTER_APPROVAL = 'AFTER_APPROVAL'
52
+ MANUAL = 'MANUAL'
53
+ SCHEDULED = 'SCHEDULED'
54
+ end
55
+
56
+ def self.type
57
+ 'appStoreVersions'
58
+ end
59
+
60
+ def on_sale?
61
+ app_store_state == AppStoreState::READY_FOR_SALE ||
62
+ app_store_state == AppStoreState::PROCESSING_FOR_APP_STORE
63
+ end
64
+
65
+ def in_review?
66
+ app_store_state == AppStoreState::IN_REVIEW
67
+ end
68
+
69
+ def preparing?
70
+ app_store_state == AppStoreState::PREPARE_FOR_SUBMISSION ||
71
+ app_store_state == AppStoreState::DEVELOPER_REJECTED
72
+ end
73
+
74
+ def rejected?
75
+ app_store_state == AppStoreState::REJECTED ||
76
+ app_store_state == AppStoreState::METADATA_REJECTED ||
77
+ app_store_state == AppStoreState::INVALID_BINARY
78
+ end
79
+
80
+ def editable?
81
+ preparing? || rejected?
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'app_status_notification/connect_api/model'
4
+
5
+ module AppStatusNotification::ConnectAPI::Model
6
+ class AppStoreVersionSubmission
7
+ include AppStatusNotification::ConnectAPI::Model
8
+
9
+ attr_accessor :can_reject
10
+
11
+ def self.type
12
+ 'appStoreVersionSubmissions'
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'app_status_notification/connect_api/model'
4
+
5
+ module AppStatusNotification::ConnectAPI::Model
6
+ class Build
7
+ include AppStatusNotification::ConnectAPI::Model
8
+
9
+ attr_accessor :version
10
+ attr_accessor :uploaded_date
11
+ attr_accessor :expiration_date
12
+ attr_accessor :expired
13
+ attr_accessor :min_os_version
14
+ attr_accessor :icon_asset_token
15
+ attr_accessor :processing_state
16
+ attr_accessor :uses_non_exempt_encryption
17
+
18
+ attr_accessor :app
19
+ attr_accessor :beta_app_review_submission
20
+ attr_accessor :beta_build_metrics
21
+ attr_accessor :build_beta_detail
22
+ attr_accessor :pre_release_version
23
+
24
+ ESSENTIAL_INCLUDES = 'app,preReleaseVersion'
25
+
26
+ def self.type
27
+ 'builds'
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'app_status_notification/connect_api/model'
4
+
5
+ module AppStatusNotification::ConnectAPI::Model
6
+ class PreReleaseVersion
7
+ include AppStatusNotification::ConnectAPI::Model
8
+
9
+ attr_accessor :version
10
+ attr_accessor :platform
11
+
12
+ def self.type
13
+ return 'preReleaseVersions'
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module AppStatusNotification
6
+ class ConnectAPI
7
+ class Response
8
+ include Enumerable
9
+ extend Forwardable
10
+
11
+ attr_reader :response, :connection
12
+
13
+ def initialize(response, connection)
14
+ @response = response
15
+ @connection = connection
16
+ end
17
+
18
+ def_delegators :@response, :status, :headers
19
+
20
+ def request_url
21
+ @response.env.url
22
+ end
23
+
24
+ def rate
25
+ return {} unless value = response.headers[:x_rate_limit]
26
+
27
+ value.split(';').inject({}) do |r, q|
28
+ k, v = q.split(':')
29
+ case k
30
+ when 'user-hour-lim'
31
+ r.merge!(limit: v)
32
+ when 'user-hour-rem'
33
+ r.merge!(remaining: v)
34
+ end
35
+ end
36
+ end
37
+
38
+ def next_link
39
+ return if response.nil?
40
+
41
+ links = response[:links] || {}
42
+ links[:next]
43
+ end
44
+
45
+ # def all_pages
46
+ # next_pages(count: 0)
47
+ # end
48
+
49
+ # def next_pages(count: 1)
50
+ # count = count.to_i
51
+ # count = 0 if count <= 0
52
+
53
+ # responses = [self]
54
+ # counter = 0
55
+
56
+ # resp = self
57
+ # loop do
58
+ # resp = resp.next_page
59
+ # break if resp.nil?
60
+
61
+ # responses << resp
62
+ # counter += 1
63
+
64
+ # break if counter >= count
65
+ # end
66
+
67
+ # responses
68
+ # end
69
+
70
+ # def next_url
71
+ # return if response.nil?
72
+
73
+ # links = response[:links] || {}
74
+ # links[:next]
75
+ # end
76
+
77
+ # def next_page
78
+ # return unless url = next_url
79
+
80
+ # Response.new(connection.get(url), connection)
81
+ # end
82
+
83
+ def to_model
84
+ to_models.first
85
+ end
86
+
87
+ def to_models
88
+ return [] if response.nil?
89
+
90
+ model_or_models = ConnectAPI::Parser.parse(response, rate)
91
+ [model_or_models].flatten
92
+ end
93
+
94
+ def each(&block)
95
+ to_models.each do |model|
96
+ yield(model)
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+