tiny_appstore_connect 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4c0a0ee36290dce8d6fec8d64801bda09fa15e27668d755e058739a1bdc04d79
4
+ data.tar.gz: 2d5b81ef78cd0c3a004df7e625044f01761582b87f3ce5e244a168cc9b3c6283
5
+ SHA512:
6
+ metadata.gz: 614467c40dfdc1e23aebc2fd823a0becb210abc6aaebeaeafea348f98826431f408a2476adfc39ab319f561d779838527ab8c02607fec6c7ba5c30c4dcc774b2
7
+ data.tar.gz: b8380dfa0bf0e96c580b55cd5651837cf5767f2b28362f2bc554c181d4d0e3860f5be71f5fea8968e9c0b092b733d62ec404538716e17c241df81d2936ade76c
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,13 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.6
3
+
4
+ Style/StringLiterals:
5
+ Enabled: true
6
+ EnforcedStyle: double_quotes
7
+
8
+ Style/StringLiteralsInInterpolation:
9
+ Enabled: true
10
+ EnforcedStyle: double_quotes
11
+
12
+ Layout/LineLength:
13
+ Max: 120
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2022-04-24
4
+
5
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in appstore_connect.gemspec
6
+ gemspec
7
+
8
+ gem 'awesome_print', '~> 2.0.0.pre2'
9
+ gem 'rake', '~> 13.0'
10
+ gem 'rspec', '~> 3.0'
11
+ gem 'rubocop', '~> 1.21'
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 icyleaf
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # Tiny Appstore Connect
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/appstore_connect`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'tiny_appstore_connect'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle install
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install tiny_appstore_connect
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/appstore_connect.
36
+
37
+ ## License
38
+
39
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require 'rubocop/rake_task'
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
13
+
14
+
15
+ namespace :github do
16
+ task :release do
17
+ Rake::Task['build'].invoke
18
+ Rake::Task['release:guard_clean'].invoke
19
+ Rake::Task['release:source_control_push'].invoke
20
+ Rake::Task['github:push'].invoke
21
+ end
22
+
23
+ task :push do
24
+ helper = Bundler::GemHelper.new
25
+ # puts helper.base
26
+ # puts helper.gemspec
27
+ puts "#{helper.send(:name)}-*.gem"
28
+ puts helper.send(:name)
29
+ puts helper.send(:built_gem_path)
30
+ puts Gem::Util.glob_files_in_dir("#{helper.send(:name)}-*.gem", helper.base)
31
+ # puts helper.send(:rubygem_push, helper.send(:built_gem_path))
32
+ # helper.send(:rubygem_push, '.')
33
+ # cmd = [*gem_command, "push", path]
34
+ # cmd << "--key" << gem_key if gem_key
35
+ # cmd << "--host" << allowed_push_host if allowed_push_host
36
+
37
+ # Bundler.ui.debug(cmd)
38
+ end
39
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Inspried from fastlane/spaceship
4
+ # License by fastlane team (https://github.com/fastlane/fastlane)
5
+
6
+ require 'faraday'
7
+ if Gem::Dependency.new('', '< 2.0.0').match?('', Faraday::VERSION)
8
+ require 'faraday_middleware'
9
+ end
10
+
11
+ module TinyAppstoreConnect
12
+ class Client
13
+ Dir[File.expand_path('clients/*.rb', __dir__)].each { |f| require f }
14
+
15
+ ENDPOINT = 'https://api.appstoreconnect.apple.com/v1'
16
+
17
+ include App
18
+ include AppStoreVersion
19
+ include Build
20
+ include Device
21
+
22
+ attr_reader :connection
23
+
24
+ def initialize(**kargs)
25
+ configure_connection(**kargs)
26
+ end
27
+
28
+ %w[get post patch delete].each do |method|
29
+ define_method method do |path, options = {}|
30
+ params = options.dup
31
+
32
+ if %w[post patch].include?(method)
33
+ body = params[:body].to_json
34
+ headers = params[:headers] || {}
35
+ headers[:content_type] ||= 'application/json'
36
+ validates connection.send(method, path, body, headers)
37
+ else
38
+ validates connection.send(method, path, params)
39
+ end
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def validates(response)
46
+ puts response.body
47
+ case response.status
48
+ when 200, 201, 204
49
+ # 200: get requests
50
+ # 201: post requests
51
+ # 204: patch, delete requests
52
+ handle_response(response)
53
+ else
54
+ raise ConnectAPIError.parse(response)
55
+ end
56
+ end
57
+
58
+ def handle_response(response)
59
+ response = Response.new(response, connection)
60
+
61
+ if (remaining = response.rate[:remaining]) && remaining.to_i.zero?
62
+ raise RateLimitExceededError, "Request limit reached #{response.rate[:limit]}
63
+ in the previous 60 minutes with url: #{response.request_url}"
64
+ end
65
+
66
+ response
67
+ end
68
+
69
+ def configure_connection(**kargs)
70
+ @token = Token.new(**kargs)
71
+ endpoint = kargs[:endpoint] || ENDPOINT
72
+
73
+ connection_opts= {
74
+ request: {
75
+ timeout: (kargs[:timeout] || 300).to_i,
76
+ open_timeout: (kargs[:timeout] || 300).to_i
77
+ }
78
+ }
79
+
80
+ @connection = Faraday.new(endpoint) do |builder|
81
+ builder.headers[:user_agent] = "TinyAppstoreConnect v#{TinyAppstoreConnect::VERSION}"
82
+ builder.headers[:accept] = 'application/json'
83
+ builder.request :url_encoded
84
+ builder.request :authorization, 'Bearer', -> { @token.token }
85
+ builder.response :json, content_type: /\bjson$/
86
+
87
+ if kargs[:debug] || ENV['ASC_DEBUG']
88
+ builder.proxy = kargs[:proxy] || ENV['ASC_PROXY'] ||'https://127.0.0.1:8888'
89
+ builder.ssl[:verify_mode] = OpenSSL::SSL::VERIFY_NONE
90
+ builder.response :logger, nil, { log_level: :debug }
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Inspried from fastlane/spaceship
4
+ # License by fastlane team (https://github.com/fastlane/fastlane)
5
+
6
+ class TinyAppstoreConnect::Client
7
+ module App
8
+ def apps(query = {})
9
+ get('apps', query)
10
+ end
11
+
12
+ def app(id, query = {})
13
+ get("apps/#{id}", query).to_model
14
+ end
15
+
16
+ def app_versions(id, query = {})
17
+ get("apps/#{id}/appStoreVersions", query)
18
+ end
19
+
20
+ def app_edit_version(id, includes: AppStoreVersion::ESSENTIAL_INCLUDES)
21
+ filters = {
22
+ appStoreState: [
23
+ AppStoreState::PREPARE_FOR_SUBMISSION,
24
+ AppStoreState::DEVELOPER_REJECTED,
25
+ AppStoreState::REJECTED,
26
+ AppStoreState::METADATA_REJECTED,
27
+ AppStoreState::WAITING_FOR_REVIEW,
28
+ AppStoreState::INVALID_BINARY,
29
+ AppStoreState::IN_REVIEW,
30
+ AppStoreState::PENDING_DEVELOPER_RELEASE
31
+ ].join(',')
32
+ }
33
+
34
+ app_versions(id, include: includes, filter: filters).to_model
35
+ end
36
+
37
+ def app_live_version(id, includes: ESSENTIAL_INCLUDES)
38
+ filters = {
39
+ appStoreState: AppStoreState::READY_FOR_SALE
40
+ }
41
+
42
+ app_versions(id, include: includes, filter: filters).to_model
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Inspried from fastlane/spaceship
4
+ # License by fastlane team (https://github.com/fastlane/fastlane)
5
+
6
+ class TinyAppstoreConnect::Client
7
+ module AppStoreVersion
8
+ # def versions(query = {})
9
+ # get('appStoreVersions', query)
10
+ # end
11
+
12
+ def version(id, query = {})
13
+ get("appStoreVersions/#{id}", query)
14
+ end
15
+
16
+ def select_version_build(id, build_id:)
17
+ body = {
18
+ data: {
19
+ type: 'builds',
20
+ id: build_id
21
+ }
22
+ }
23
+
24
+ patch("appStoreVersions/#{id}/relationships/build", body: body)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Inspried from fastlane/spaceship
4
+ # License by fastlane team (https://github.com/fastlane/fastlane)
5
+
6
+ class TinyAppstoreConnect::Client
7
+ module Build
8
+ def app_latest_build(id)
9
+ app_builds(id, limit: 1).to_model
10
+ end
11
+
12
+ def app_builds(id, **kargs)
13
+ kargs = kargs.merge(filter: { app: id })
14
+ builds(**kargs)
15
+ end
16
+
17
+ def builds(limit: 200, sort: '-uploadedDate',
18
+ includes: TinyAppstoreConnect::Model::Build::ESSENTIAL_INCLUDES,
19
+ **kargs)
20
+
21
+ kargs = kargs.merge(limit: limit)
22
+ .merge(sort: sort)
23
+ .merge(include: includes)
24
+
25
+ get('builds', **kargs)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Inspried from fastlane/spaceship
4
+ # License by fastlane team (https://github.com/fastlane/fastlane)
5
+
6
+ class TinyAppstoreConnect::Client
7
+ module Device
8
+ def devices(filter: {}, includes: nil, limit: nil, sort: nil)
9
+ kargs = {
10
+ include: includes,
11
+ limit: limit,
12
+ sort: sort
13
+ }.delete_if { |_, v| v.nil? }
14
+
15
+ get('devices', **kargs)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,169 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Inspried from fastlane/spaceship
4
+ # License by fastlane team (https://github.com/fastlane/fastlane)
5
+
6
+ module TinyAppstoreConnect
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 = "#{underscore(key.to_s)}=".to_sym
46
+ self.send(method, value) if self.respond_to?(method)
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def underscore(camel_cased_word)
53
+ return camel_cased_word.to_s unless /[A-Z-]/.match?(camel_cased_word)
54
+
55
+ word = ''.dup
56
+ camel_cased_word.each_char do |char|
57
+ if /[A-Z]/.match?(char)
58
+ word << '_' << char.downcase
59
+ else
60
+ word << char
61
+ end
62
+ end
63
+
64
+ word
65
+ end
66
+ end
67
+
68
+ module Parser
69
+ class << self
70
+ attr_accessor :types
71
+ attr_accessor :types_cache
72
+ end
73
+
74
+ def self.parse(response, rate)
75
+ body = response.body
76
+ data = body['data']
77
+ raise ConnectAPIError, 'No data' unless data
78
+
79
+ included = body['included'] || []
80
+ if data.kind_of?(Hash)
81
+ inflate_model(data, included, rate: rate)
82
+ elsif data.kind_of?(Array)
83
+ return data.map do |model_data|
84
+ inflate_model(model_data, included, rate: rate)
85
+ end
86
+ else
87
+ raise ConnectAPIError, "'data' is neither a hash nor an array"
88
+ end
89
+ end
90
+
91
+ def self.inflate_model(model_data, included, rate: {})
92
+ # Find class
93
+ type_class = find_class(model_data)
94
+ raise "No type class found for #{model_data['type']}" unless type_class
95
+
96
+ # Get id and attributes needed for inflating
97
+ id = model_data['id']
98
+ attributes = model_data['attributes']
99
+
100
+ # Instantiate object and inflate relationships
101
+ relationships = model_data['relationships'] || []
102
+ type_instance = type_class.new(id, attributes, rate)
103
+
104
+ inflate_model_relationships(type_instance, relationships, included)
105
+ end
106
+
107
+ def self.find_class(model_data)
108
+ # Initialize cache
109
+ @types_cache ||= {}
110
+
111
+ # Find class in cache
112
+ type_string = model_data['type']
113
+ type_class = @types_cache[type_string]
114
+ return type_class if type_class
115
+
116
+ # Find class in array
117
+ type_class = @types.find do |type|
118
+ type.type == type_string
119
+ end
120
+
121
+ # Cache and return class
122
+ type_class
123
+ end
124
+
125
+ def self.inflate_model_relationships(type_instance, relationships, included)
126
+ # Relationship attributes to set
127
+ attributes = {}
128
+
129
+ # 1. Iterate over relationships
130
+ # 2. Find id and type
131
+ # 3. Find matching id and type in included
132
+ # 4. Inflate matching data and set in attributes
133
+ relationships.each do |key, value|
134
+ # Validate data exists
135
+ value_data_or_datas = value['data']
136
+ next unless value_data_or_datas
137
+
138
+ # Map an included data object
139
+ map_data = lambda do |value_data|
140
+ id = value_data['id']
141
+ type = value_data['type']
142
+
143
+ relationship_data = included.find do |included_data|
144
+ id == included_data['id'] && type == included_data['type']
145
+ end
146
+
147
+ inflate_model(relationship_data, included) if relationship_data
148
+ end
149
+
150
+ # Map a hash or an array of data
151
+ if value_data_or_datas.kind_of?(Hash)
152
+ attributes[key] = map_data.call(value_data_or_datas)
153
+ elsif value_data_or_datas.kind_of?(Array)
154
+ attributes[key] = value_data_or_datas.map(&map_data)
155
+ end
156
+ end
157
+
158
+ type_instance.update_attributes(attributes)
159
+ type_instance
160
+ end
161
+ end
162
+ end
163
+
164
+ require_relative 'models/app'
165
+ require_relative 'models/app_store_version'
166
+ require_relative 'models/build'
167
+ require_relative 'models/pre_release_version'
168
+ require_relative 'models/app_store_version_submission'
169
+ require_relative 'models/device'
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Inspried from fastlane/spaceship
4
+ # License by fastlane team (https://github.com/fastlane/fastlane)
5
+
6
+ module TinyAppstoreConnect::Model
7
+ class App
8
+ include TinyAppstoreConnect::Model
9
+
10
+ attr_accessor :name
11
+ attr_accessor :bundle_id
12
+ attr_accessor :sku
13
+ attr_accessor :primary_locale
14
+ attr_accessor :is_opted_in_to_distribute_ios_app_on_mac_app_store
15
+ attr_accessor :removed
16
+ attr_accessor :is_aag
17
+ attr_accessor :available_in_new_territories
18
+ attr_accessor :content_rights_declaration
19
+
20
+ # include
21
+ attr_accessor :app_store_versions
22
+ attr_accessor :builds
23
+
24
+ def self.type
25
+ 'apps'
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Inspried from fastlane/spaceship
4
+ # License by fastlane team (https://github.com/fastlane/fastlane)
5
+
6
+ module TinyAppstoreConnect::Model
7
+ class AppStoreVersion
8
+ include TinyAppstoreConnect::Model
9
+
10
+ attr_accessor :platform
11
+ attr_accessor :version_string
12
+ attr_accessor :app_store_state
13
+ attr_accessor :release_type
14
+ attr_accessor :earliest_release_date # 2020-06-17T12:00:00-07:00
15
+ attr_accessor :uses_idfa
16
+ attr_accessor :downloadable
17
+ attr_accessor :created_date
18
+ attr_accessor :version
19
+ attr_accessor :uploaded_date
20
+ attr_accessor :expiration_date
21
+ attr_accessor :expired
22
+ attr_accessor :store_icon
23
+ attr_accessor :watch_store_icon
24
+ attr_accessor :copyright
25
+ attr_accessor :min_os_version
26
+
27
+ # include
28
+ attr_accessor :app
29
+ attr_accessor :app_store_version_submission
30
+ attr_accessor :build
31
+
32
+ ESSENTIAL_INCLUDES = [
33
+ 'app',
34
+ 'appStoreVersionSubmission',
35
+ 'build'
36
+ ].join(',')
37
+
38
+ module AppStoreState
39
+ READY_FOR_SALE = 'READY_FOR_SALE'
40
+ PROCESSING_FOR_APP_STORE = 'PROCESSING_FOR_APP_STORE'
41
+ PENDING_DEVELOPER_RELEASE = 'PENDING_DEVELOPER_RELEASE'
42
+ IN_REVIEW = 'IN_REVIEW'
43
+ WAITING_FOR_REVIEW = 'WAITING_FOR_REVIEW'
44
+ DEVELOPER_REJECTED = 'DEVELOPER_REJECTED'
45
+ REJECTED = 'REJECTED'
46
+ PREPARE_FOR_SUBMISSION = 'PREPARE_FOR_SUBMISSION'
47
+ METADATA_REJECTED = 'METADATA_REJECTED'
48
+ INVALID_BINARY = 'INVALID_BINARY'
49
+ end
50
+
51
+ module ReleaseType
52
+ AFTER_APPROVAL = 'AFTER_APPROVAL'
53
+ MANUAL = 'MANUAL'
54
+ SCHEDULED = 'SCHEDULED'
55
+ end
56
+
57
+ def self.type
58
+ 'appStoreVersions'
59
+ end
60
+
61
+ def on_sale?
62
+ app_store_state == AppStoreState::READY_FOR_SALE ||
63
+ app_store_state == AppStoreState::PROCESSING_FOR_APP_STORE
64
+ end
65
+
66
+ def in_review?
67
+ app_store_state == AppStoreState::IN_REVIEW
68
+ end
69
+
70
+ def preparing?
71
+ app_store_state == AppStoreState::PREPARE_FOR_SUBMISSION ||
72
+ app_store_state == AppStoreState::DEVELOPER_REJECTED
73
+ end
74
+
75
+ def rejected?
76
+ app_store_state == AppStoreState::REJECTED ||
77
+ app_store_state == AppStoreState::METADATA_REJECTED ||
78
+ app_store_state == AppStoreState::INVALID_BINARY
79
+ end
80
+
81
+ def editable?
82
+ preparing? || rejected?
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Inspried from fastlane/spaceship
4
+ # License by fastlane team (https://github.com/fastlane/fastlane)
5
+
6
+ module TinyAppstoreConnect::Model
7
+ class AppStoreVersionSubmission
8
+ include TinyAppstoreConnect::Model
9
+
10
+ attr_accessor :can_reject
11
+
12
+ def self.type
13
+ 'appStoreVersionSubmissions'
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Inspried from fastlane/spaceship
4
+ # License by fastlane team (https://github.com/fastlane/fastlane)
5
+
6
+ module TinyAppstoreConnect::Model
7
+ class Build
8
+ include TinyAppstoreConnect::Model
9
+
10
+ attr_accessor :version
11
+ attr_accessor :uploaded_date
12
+ attr_accessor :expiration_date
13
+ attr_accessor :expired
14
+ attr_accessor :min_os_version
15
+ attr_accessor :icon_asset_token
16
+ attr_accessor :processing_state
17
+ attr_accessor :uses_non_exempt_encryption
18
+
19
+ attr_accessor :app
20
+ attr_accessor :beta_app_review_submission
21
+ attr_accessor :beta_build_metrics
22
+ attr_accessor :build_beta_detail
23
+ attr_accessor :pre_release_version
24
+
25
+ ESSENTIAL_INCLUDES = 'app,preReleaseVersion'
26
+
27
+ def self.type
28
+ 'builds'
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Inspried from fastlane/spaceship
4
+ # License by fastlane team (https://github.com/fastlane/fastlane)
5
+
6
+ module TinyAppstoreConnect::Model
7
+ class Device
8
+ include TinyAppstoreConnect::Model
9
+
10
+ attr_accessor :device_class
11
+ attr_accessor :model
12
+ attr_accessor :name
13
+ attr_accessor :platform
14
+ attr_accessor :status
15
+ attr_accessor :udid
16
+ attr_accessor :added_date
17
+
18
+ # include
19
+ # attr_accessor :app
20
+ # attr_accessor :app_store_version_submission
21
+ # attr_accessor :build
22
+
23
+ module DeviceClass
24
+ APPLE_WATCH = "APPLE_WATCH"
25
+ IPAD = "IPAD"
26
+ IPHONE = "IPHONE"
27
+ IPOD = "IPOD"
28
+ APPLE_TV = "APPLE_TV"
29
+ MAC = "MAC"
30
+ end
31
+
32
+ module Status
33
+ ENABLED = "ENABLED"
34
+ DISABLED = "DISABLED"
35
+ end
36
+
37
+ def self.type
38
+ return "devices"
39
+ end
40
+
41
+ def enabled?
42
+ return status == Status::ENABLED
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Inspried from fastlane/spaceship
4
+ # License by fastlane team (https://github.com/fastlane/fastlane)
5
+
6
+ module TinyAppstoreConnect::Model
7
+ class PreReleaseVersion
8
+ include TinyAppstoreConnect::Model
9
+
10
+ attr_accessor :version
11
+ attr_accessor :platform
12
+
13
+ def self.type
14
+ return 'preReleaseVersions'
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Inspried from fastlane/spaceship
4
+ # License by fastlane team (https://github.com/fastlane/fastlane)
5
+
6
+ require 'forwardable'
7
+
8
+ module TinyAppstoreConnect
9
+ class Response
10
+ include Enumerable
11
+ extend Forwardable
12
+
13
+ attr_reader :response, :connection
14
+
15
+ def initialize(response, connection)
16
+ @response = response
17
+ @connection = connection
18
+ end
19
+
20
+ def_delegators :@response, :status, :headers
21
+
22
+ def request_url
23
+ @response.env.url
24
+ end
25
+
26
+ def rate
27
+ return {} unless value = response.headers[:x_rate_limit]
28
+
29
+ value.split(';').inject({}) do |r, q|
30
+ k, v = q.split(':')
31
+ case k
32
+ when 'user-hour-lim'
33
+ r.merge!(limit: v)
34
+ when 'user-hour-rem'
35
+ r.merge!(remaining: v)
36
+ end
37
+ end
38
+ end
39
+
40
+ def next_link
41
+ return if response.nil?
42
+
43
+ links = response[:links] || {}
44
+ links[:next]
45
+ end
46
+
47
+ # def all_pages
48
+ # next_pages(count: 0)
49
+ # end
50
+
51
+ # def next_pages(count: 1)
52
+ # count = count.to_i
53
+ # count = 0 if count <= 0
54
+
55
+ # responses = [self]
56
+ # counter = 0
57
+
58
+ # resp = self
59
+ # loop do
60
+ # resp = resp.next_page
61
+ # break if resp.nil?
62
+
63
+ # responses << resp
64
+ # counter += 1
65
+
66
+ # break if counter >= count
67
+ # end
68
+
69
+ # responses
70
+ # end
71
+
72
+ # def next_url
73
+ # return if response.nil?
74
+
75
+ # links = response[:links] || {}
76
+ # links[:next]
77
+ # end
78
+
79
+ # def next_page
80
+ # return unless url = next_url
81
+
82
+ # Response.new(connection.get(url), connection)
83
+ # end
84
+
85
+ def to_model
86
+ to_models.first
87
+ end
88
+
89
+ def to_models
90
+ return [] if response.nil?
91
+
92
+ model_or_models = Parser.parse(response, rate)
93
+ [model_or_models].flatten
94
+ end
95
+
96
+ def each(&block)
97
+ to_models.each do |model|
98
+ yield(model)
99
+ end
100
+ end
101
+ end
102
+ end
103
+
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Inspried from fastlane/spaceship
4
+ # License by fastlane team (https://github.com/fastlane/fastlane)
5
+
6
+ require 'jwt'
7
+ require 'openssl'
8
+
9
+ module TinyAppstoreConnect
10
+ class Token
11
+ # maximum expiration supported by AppStore (20 minutes)
12
+ # detail: https://developer.apple.com/documentation/appstoreconnectapi/generating_tokens_for_api_requests
13
+ DEFAULT_TOKEN_DURATION = 10 * 60
14
+ AUDIENCE = 'appstoreconnect-v1'
15
+ ALGORITHM = 'ES256'
16
+
17
+ attr_reader :issuer_id, :key_id, :private_key
18
+
19
+ def initialize(**kargs)
20
+ @duration = kargs[:duration] || DEFAULT_TOKEN_DURATION
21
+ @issuer_id = kargs[:issuer_id]
22
+ @key_id = kargs[:key_id]
23
+ @private_key = handle_private_key(kargs[:private_key])
24
+ end
25
+
26
+ def token
27
+ JWT.encode(payload, private_key, ALGORITHM, header_fields)
28
+ end
29
+
30
+ private
31
+
32
+ def expired?
33
+ @expiration < Time.now
34
+ end
35
+
36
+ def payload
37
+ return @payload if @payload && !expired?
38
+
39
+ @expiration = Time.now + @duration
40
+ @payload = {
41
+ aud: AUDIENCE,
42
+ iss: issuer_id,
43
+ exp: @expiration.to_i
44
+ }
45
+ end
46
+
47
+ def header_fields
48
+ @header_fields ||= { kid: key_id }
49
+ end
50
+
51
+ def handle_private_key(file)
52
+ io = File.file?(file) ? File.read(file) : file
53
+ OpenSSL::PKey.read(io)
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TinyAppstoreConnect
4
+ VERSION = '0.1.3'
5
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'tiny_appstore_connect/version'
4
+ require_relative 'tiny_appstore_connect/token'
5
+ require_relative 'tiny_appstore_connect/model'
6
+ require_relative 'tiny_appstore_connect/response'
7
+ require_relative 'tiny_appstore_connect/client'
8
+
9
+ module TinyAppstoreConnect
10
+ class Error < StandardError; end
11
+
12
+ class ConnectAPIError < Error
13
+ class << self
14
+ def parse(response)
15
+ errors = response.body['errors']
16
+ case response.status
17
+ when 401
18
+ # Unauthorized
19
+ InvalidUserCredentialsError.from_errors(errors)
20
+ when 403
21
+ # The API key in use does not allow this request
22
+ ForbiddenError.from_errors(errors)
23
+ when 404
24
+ # Not found resource
25
+ NotFoundError.from_errors(errors)
26
+ when 409
27
+ # Invaild request entity
28
+ InvalidEntityError.from_errors(errors)
29
+ when 429
30
+ # 429 Rate Limit Exceeded
31
+ RateLimitExceededError.from_errors(errors)
32
+ else
33
+ ConnectAPIError.from_errors(errors)
34
+ end
35
+ end
36
+
37
+ def from_errors(errors)
38
+ message = ["Check errors(#{errors.size}) from response:"]
39
+ errors.each_with_index do |error, i|
40
+ message << "#{i + 1} - [#{error['status']}] #{error['title']}: #{error['detail']} in #{error['source']}"
41
+ end
42
+
43
+ new(message)
44
+ end
45
+ end
46
+ end
47
+
48
+ class RateLimitExceededError < ConnectAPIError; end
49
+ class InvalidEntityError < ConnectAPIError; end
50
+ class InvalidUserCredentialsError < ConnectAPIError; end
51
+ class ForbiddenError < ConnectAPIError; end
52
+ class NotFoundError < ConnectAPIError; end
53
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/tiny_appstore_connect/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'tiny_appstore_connect'
7
+ spec.version = TinyAppstoreConnect::VERSION
8
+ spec.authors = ['icyleaf']
9
+ spec.email = ['icyleaf.cn@gmail.com']
10
+
11
+ spec.summary = 'Tiny AppStore Connect API'
12
+ spec.description = 'Tiny AppStore Connect API'
13
+ spec.homepage = 'https://github.com/icyleaf/tiny_appstore_connect'
14
+ spec.license = 'MIT'
15
+ spec.required_ruby_version = '>= 2.6.0'
16
+
17
+ spec.metadata['homepage_uri'] = spec.homepage
18
+ spec.metadata['github_repo'] = 'ssh://github.com/icyleaf/tiny_appstore_connect'
19
+
20
+
21
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
22
+ `git ls-files -z`.split("\x0").reject do |f|
23
+ (f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
24
+ end
25
+ end
26
+ spec.bindir = 'exe'
27
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ['lib']
29
+
30
+ spec.add_dependency 'faraday', '>= 1.10.0', '< 3.0'
31
+ spec.add_dependency 'jwt', '>= 1.4', '<= 2.2.1'
32
+ end
metadata ADDED
@@ -0,0 +1,109 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tiny_appstore_connect
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.3
5
+ platform: ruby
6
+ authors:
7
+ - icyleaf
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2022-04-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 1.10.0
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '3.0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: 1.10.0
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '3.0'
33
+ - !ruby/object:Gem::Dependency
34
+ name: jwt
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '1.4'
40
+ - - "<="
41
+ - !ruby/object:Gem::Version
42
+ version: 2.2.1
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '1.4'
50
+ - - "<="
51
+ - !ruby/object:Gem::Version
52
+ version: 2.2.1
53
+ description: Tiny AppStore Connect API
54
+ email:
55
+ - icyleaf.cn@gmail.com
56
+ executables: []
57
+ extensions: []
58
+ extra_rdoc_files: []
59
+ files:
60
+ - ".rspec"
61
+ - ".rubocop.yml"
62
+ - CHANGELOG.md
63
+ - Gemfile
64
+ - LICENSE.txt
65
+ - README.md
66
+ - Rakefile
67
+ - lib/tiny_appstore_connect.rb
68
+ - lib/tiny_appstore_connect/client.rb
69
+ - lib/tiny_appstore_connect/clients/app.rb
70
+ - lib/tiny_appstore_connect/clients/app_store_version.rb
71
+ - lib/tiny_appstore_connect/clients/build.rb
72
+ - lib/tiny_appstore_connect/clients/device.rb
73
+ - lib/tiny_appstore_connect/model.rb
74
+ - lib/tiny_appstore_connect/models/app.rb
75
+ - lib/tiny_appstore_connect/models/app_store_version.rb
76
+ - lib/tiny_appstore_connect/models/app_store_version_submission.rb
77
+ - lib/tiny_appstore_connect/models/build.rb
78
+ - lib/tiny_appstore_connect/models/device.rb
79
+ - lib/tiny_appstore_connect/models/pre_release_version.rb
80
+ - lib/tiny_appstore_connect/response.rb
81
+ - lib/tiny_appstore_connect/token.rb
82
+ - lib/tiny_appstore_connect/version.rb
83
+ - tiny_appstore_connect.gemspec
84
+ homepage: https://github.com/icyleaf/tiny_appstore_connect
85
+ licenses:
86
+ - MIT
87
+ metadata:
88
+ homepage_uri: https://github.com/icyleaf/tiny_appstore_connect
89
+ github_repo: ssh://github.com/icyleaf/tiny_appstore_connect
90
+ post_install_message:
91
+ rdoc_options: []
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: 2.6.0
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ requirements: []
105
+ rubygems_version: 3.2.32
106
+ signing_key:
107
+ specification_version: 4
108
+ summary: Tiny AppStore Connect API
109
+ test_files: []