app_status_notification 0.9.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
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
+