kt-paperclip-aliyun 1.0.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c378e96208b951136a4aa554ea68e7e17c0763bdb9ce60f085047cf2bead67d5
4
+ data.tar.gz: 026302ed6206ca9f4c2b380ec5512bfc6fedb0e493a38e9057f8fcea54b624ba
5
+ SHA512:
6
+ metadata.gz: dff31b7e8579232688b134c1dd5445b112130eba06d551f1f85447d19e170d6505ffb2fa77c11357f0653922b333cce689304d17b8861c43165ff7e2acea18e0
7
+ data.tar.gz: 5e45e9e02e5ba491ed8e20856837e7b20e1cf521ad273f71ac941ea01b3494ae42b040a175dfe29b3f5858873d3c7eb870913914778de815ff856ea87863d318
data/CHANGELOG.md ADDED
@@ -0,0 +1,53 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.0.0] - 2025-12-09
9
+
10
+ ### BREAKING CHANGES
11
+ - **Gem renamed from `paperclip-storage-aliyun` to `kt-paperclip-aliyun`**
12
+ - Main require file changed from `paperclip-storage-aliyun` to `kt-paperclip-aliyun`
13
+ - Update your Gemfile: `gem 'kt-paperclip-aliyun'` instead of `gem 'paperclip-storage-aliyun'`
14
+ - Update your requires: `require 'kt-paperclip-aliyun'` instead of `require 'paperclip-storage-aliyun'`
15
+ - **Repository ownership transferred to 7a6163**
16
+
17
+ ### Added
18
+ - Ruby 3.0+ support (tested on Ruby 2.7, 3.0, 3.1, 3.2, 3.3)
19
+ - GitHub Actions CI/CD workflow replacing Travis CI
20
+ - CodeCov integration for code coverage tracking
21
+ - WebMock integration for testing without real Aliyun credentials
22
+ - Comprehensive test suite improvements
23
+ - Gem metadata for better RubyGems.org integration
24
+
25
+ ### Changed
26
+ - **BREAKING**: Minimum Ruby version is now 2.7.0 (was 2.6.0)
27
+ - Updated `rest-client` dependency from `>= 1.6.7` to `>= 2.0.0` for Ruby 3 compatibility
28
+ - Updated test dependencies:
29
+ - `activerecord` from `~> 4.0.0` to `>= 5.0`
30
+ - `rspec` from `~> 3.3.0` to `~> 3.10`
31
+ - `rubocop` from `~> 0.34.2` to `~> 1.0`
32
+ - Gem source changed from `gems.ruby-china.com` to `rubygems.org`
33
+ - Replaced deprecated `URI.encode` with `URI::DEFAULT_PARSER.escape` for Ruby 3.0+ compatibility
34
+
35
+ ### Fixed
36
+ - Fixed Ruby 3.0+ compatibility issues with URI encoding
37
+ - Fixed all test failures by improving test isolation and mock setup
38
+ - Renamed test fixture file from Chinese characters to `chinese-name.jpg` for better compatibility
39
+
40
+ ### Deprecated
41
+ - Travis CI configuration (replaced with GitHub Actions)
42
+
43
+ ## [0.1.6] - 2020-04-20
44
+
45
+ ### Fixed
46
+ - Fixed argument pollution in #get_endpoint
47
+
48
+ ## Previous versions
49
+
50
+ See git history for changes in versions prior to 1.0.0.
51
+
52
+ [1.0.0]: https://github.com/7a6163/kt-paperclip-aliyun/compare/v0.1.6...v1.0.0
53
+ [0.1.6]: https://github.com/Martin91/paperclip-storage-aliyun/compare/v0.1.5...v0.1.6
data/README.md ADDED
@@ -0,0 +1,91 @@
1
+ [![Ruby](https://github.com/7a6163/kt-paperclip-aliyun/actions/workflows/ruby.yml/badge.svg)](https://github.com/7a6163/kt-paperclip-aliyun/actions/workflows/ruby.yml)
2
+ [![codecov](https://codecov.io/gh/7a6163/kt-paperclip-aliyun/branch/master/graph/badge.svg)](https://codecov.io/gh/7a6163/kt-paperclip-aliyun)
3
+ [![Gem Version](https://badge.fury.io/rb/kt-paperclip-aliyun.svg)](https://badge.fury.io/rb/kt-paperclip-aliyun)
4
+
5
+ Aliyun Open Storage Service for Paperclip
6
+ ===
7
+ This gem implement the support for [Aliyun open storage service(OSS)](http://oss.aliyun.com) to [Paperclip](https://github.com/thoughtbot/paperclip).
8
+
9
+ #### Ruby Version Support
10
+ - Ruby 2.7+
11
+ - Ruby 3.0+
12
+ - Ruby 3.1+
13
+ - Ruby 3.2+
14
+ - Ruby 3.3+
15
+
16
+ #### Installation
17
+ ```shell
18
+ gem install kt-paperclip-aliyun
19
+ ```
20
+ Or, if you are using a bundler, you can append the following line into your **Gemfile**:
21
+ ```ruby
22
+ gem 'kt-paperclip-aliyun'
23
+ ```
24
+
25
+ #### Configuration
26
+ In order to make all the things work, you should do some important configurations through a initializer:
27
+
28
+ If you are developing a Rails application, you can append a new initializer like:
29
+ ```ruby
30
+ # [rails_root]/config/initializers/paperclip-aliyun-configuration.rb
31
+ Paperclip::Attachment.default_options[:aliyun] = {
32
+ access_id: '3VL9XMho8iCushj8',
33
+ access_key: 'VAUI2q7Tc6yTh1jr3kBsEUzZ84gEa2',
34
+ bucket: 'xx-test',
35
+ data_center: 'cn-hangzhou',
36
+ internal: false,
37
+ protocol: 'https',
38
+ protocol_relative_url: false
39
+ }
40
+ ```
41
+
42
+ Then, in the model which defines the attachment, specify your storage and other options, for example:
43
+ ```ruby
44
+ # [rails_root]/app/models/image.rb
45
+ class Image < ActiveRecord::Base
46
+ has_attached_file :attachment, {
47
+ storage: :aliyun,
48
+ styles: { thumbnail: "60x60#"},
49
+ path: 'public/system/:class/:attachment/:id_partition/:style/:filename',
50
+ url: ':aliyun_upload_url'
51
+ }
52
+ end
53
+ ```
54
+
55
+ Similar to Paperclip::Storage::S3, there are four options for the url by now:
56
+ - `:aliyun_upload_url` : the url based on the options you give
57
+ - `:aliyun_internal_url` : the internal url, no matter what `options[:aliyun][:internal]` is
58
+ - `:aliyun_external_url` : the external url, no matter what `options[:aliyun][:internal]` is
59
+ - `:aliyun_alias_url` : the alias url based on the `host_alias` you give, typically used together with CDN
60
+
61
+ Please note the values above are all strings, not symbols. You could still make your own url if only you know what you are doing.
62
+
63
+ #### Data Centers
64
+ A list of available regions can be found at [https://intl.aliyun.com/help/doc-detail/31837.htm](https://intl.aliyun.com/help/doc-detail/31837.htm).
65
+ You can use the "Region Expression" column value as it is for the data center, or you can remove the "oss-" prefix. For example: `oss-cn-hangzhou` and `cn-hangzhou` are both valid options.
66
+
67
+ #### Development & Testing
68
+
69
+ Run tests with:
70
+ ```shell
71
+ bundle exec rspec
72
+ ```
73
+
74
+ By default, tests run with mocked Aliyun OSS requests (using WebMock). No real credentials are required.
75
+
76
+ If you want to run tests against real Aliyun OSS:
77
+ ```shell
78
+ export OSS_ACCESS_ID='your_access_key_id'
79
+ export OSS_ACCESS_KEY='your_access_key_secret'
80
+ bundle exec rspec
81
+ ```
82
+
83
+ #### Contributing
84
+
85
+ 1. Fork the repository
86
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
87
+ 3. Make your changes and add tests
88
+ 4. Run the test suite (`bundle exec rspec`)
89
+ 5. Commit your changes (`git commit -am 'Add some feature'`)
90
+ 6. Push to the branch (`git push origin my-new-feature`)
91
+ 7. Create a Pull Request
@@ -0,0 +1,222 @@
1
+ require 'openssl'
2
+ require 'digest/md5'
3
+ require 'rest-client'
4
+ require 'base64'
5
+ require 'uri'
6
+ require 'aliyun/data_center'
7
+
8
+ module Aliyun
9
+ class Connection
10
+ include DataCenter
11
+
12
+ # The upload host according to the connection configurations
13
+ attr_reader :aliyun_upload_host
14
+ # The internal host
15
+ attr_reader :aliyun_internal_host
16
+ # The external host
17
+ attr_reader :aliyun_external_host
18
+ # The alias host
19
+ attr_reader :aliyun_alias_host
20
+
21
+ attr_reader :aliyun_protocol
22
+
23
+ attr_reader :aliyun_protocol_relative_url
24
+
25
+ # Initialize the OSS connection
26
+ #
27
+ # @param [Hash] An options to specify connection details
28
+ # @option access_id [String] used to set "Authorization" request header
29
+ # @option access_key [String] the access key
30
+ # @option bucket [String] bucket used to access
31
+ # @option data_center [String] available data center name, e.g. 'cn-hangzhou'
32
+ # @option internal [true, false] if the service should be accessed through internal network
33
+ # @option host_alias [String] the alias of the host, such as the CDN domain name
34
+ # @option protocol [String] 'http' or 'https', default to 'http'
35
+ # @option protocol_relative_url [true, false] if to use protocol relative url, https://en.wikipedia.org/wiki/Wikipedia:Protocol-relative_URL
36
+ # @note both access_id and acces_key are related to authorization algorithm:
37
+ # https://docs.aliyun.com/#/pub/oss/api-reference/access-control&signature-header
38
+ def initialize(options = {})
39
+ @aliyun_access_id = options[:access_id]
40
+ @aliyun_access_key = options[:access_key]
41
+ @aliyun_bucket = options[:bucket]
42
+ @aliyun_protocol_relative_url = !!options[:protocol_relative_url]
43
+ @aliyun_protocol = options[:protocol] || 'http'
44
+
45
+ @aliyun_upload_host = "#{@aliyun_bucket}.#{get_endpoint(options)}"
46
+ @aliyun_internal_host = "#{@aliyun_bucket}.#{get_endpoint(options.merge(internal: true))}"
47
+ @aliyun_external_host = "#{@aliyun_bucket}.#{get_endpoint(options.merge(internal: false))}"
48
+ @aliyun_alias_host = options[:host_alias] || @aliyun_upload_host
49
+ end
50
+
51
+ # Return the meta informations for a file specified by the path
52
+ # https://docs.aliyun.com/#/pub/oss/api-reference/object&HeadObject
53
+ #
54
+ # @param path [String] the path of file storaged in Aliyun OSS
55
+ # @return [Hash] the meta data of the file
56
+ # @note the example headers will be like:
57
+ #
58
+ # {
59
+ # {:date=>"Sun, 02 Aug 2015 02:42:45 GMT",
60
+ # :content_type=>"image/jpg",
61
+ # :content_length=>"125198",
62
+ # :connection=>"close",
63
+ # :accept_ranges=>"bytes",
64
+ # :etag=>"\"336262A42E5B99AFF5B8BC66611FC156\"",
65
+ # :last_modified=>"Sun, 01 Dec 2013 16:39:57 GMT",
66
+ # :server=>"AliyunOSS",
67
+ # :x_oss_object_type=>"Normal",
68
+ # :x_oss_request_id=>"55BD83A5D4C05BDFF4A329E0"}}
69
+ #
70
+ def head(path)
71
+ path = format_path(path)
72
+ bucket_path = get_bucket_path(path)
73
+ date = gmtdate
74
+ headers = {
75
+ 'Host' => @aliyun_upload_host,
76
+ 'Date' => date,
77
+ 'Authorization' => sign('HEAD', bucket_path, '', '', date)
78
+ }
79
+ url = path_to_url(path)
80
+ RestClient.head(url, headers).headers
81
+ rescue RestClient::ResourceNotFound
82
+ {}
83
+ end
84
+
85
+ # Upload File to Aliyun OSS
86
+ # https://docs.aliyun.com/#/pub/oss/api-reference/object&PutObject
87
+ #
88
+ # @param path [String] the target storing path on the oss
89
+ # @param file [File] an instance of File represents a file to be uploaded
90
+ # @param options [Hash]
91
+ # - content_type - MimeType value for the file, default is "image/jpg"
92
+ #
93
+ # @return [String] The downloadable url of the uploaded file
94
+ # @return [nil] if the uploading failed
95
+ def put(path, file, options = {})
96
+ path = format_path(path)
97
+ bucket_path = get_bucket_path(path)
98
+ content_md5 = Digest::MD5.file(file).base64digest
99
+ content_type = options[:content_type] || 'image/jpg'
100
+ date = gmtdate
101
+ url = path_to_url(path)
102
+ auth_sign = sign('PUT', bucket_path, content_md5, content_type, date)
103
+ headers = {
104
+ 'Authorization' => auth_sign,
105
+ 'Content-Md5' => content_md5,
106
+ 'Content-Type' => content_type,
107
+ 'Content-Length' => file.size,
108
+ 'Date' => date,
109
+ 'Host' => @aliyun_upload_host,
110
+ 'Expect' => '100-Continue'
111
+ }
112
+ response = RestClient.put(url, file, headers)
113
+ response.code == 200 ? path_to_url(path) : nil
114
+ end
115
+
116
+ # Delete a file from the OSS
117
+ # https://docs.aliyun.com/#/pub/oss/api-reference/object&DeleteObject
118
+ #
119
+ # @param path [String] the path to retrieve the file on remote storage
120
+ # @return [String] the expired url to the file, if the file deleted successfully
121
+ # @return [nil] if the delete operation failed
122
+ def delete(path)
123
+ path = format_path(path)
124
+ bucket_path = get_bucket_path(path)
125
+ date = gmtdate
126
+ headers = {
127
+ 'Host' => @aliyun_upload_host,
128
+ 'Date' => date,
129
+ 'Authorization' => sign('DELETE', bucket_path, '', '', date)
130
+ }
131
+ url = path_to_url(path)
132
+ response = RestClient.delete(url, headers)
133
+ response.code == 204 ? url : nil
134
+ end
135
+
136
+ # Download the file from OSS
137
+ # https://docs.aliyun.com/#/pub/oss/api-reference/object&GetObject
138
+ #
139
+ # @param path [String] the path to retrieve the file on remote storage
140
+ # @return [?] the file content consist of bytes
141
+ def get(path)
142
+ path = format_path(path)
143
+ bucket_path = get_bucket_path(path)
144
+ date = gmtdate
145
+ headers = {
146
+ 'Host' => @aliyun_upload_host,
147
+ 'Date' => date,
148
+ 'Authorization' => sign('GET', bucket_path, '', '', date)
149
+ }
150
+ url = path_to_url(path)
151
+ response = RestClient.get(url, headers)
152
+ response.body
153
+ end
154
+
155
+ # Determine if the file exists on the OSS
156
+ # https://docs.aliyun.com/#/pub/oss/api-reference/object&HeadObject
157
+ #
158
+ # @param path [String] the path to retrieve the file on remote storage
159
+ # @return [true] if file exists
160
+ # @return [false] if file could not be found
161
+ def exists?(path)
162
+ head(path).empty? ? false : true
163
+ end
164
+
165
+ # The GMT format time referenced from HTTP 1.1
166
+ # https://docs.aliyun.com/#/pub/oss/api-reference/public-header
167
+ #
168
+ # @return [String] a string represents the formated time, e.g. "Wed, 05 Sep. 2012 23:00:00 GMT"
169
+ def gmtdate
170
+ Time.now.gmtime.strftime('%a, %d %b %Y %H:%M:%S GMT')
171
+ end
172
+
173
+ # remove leading slashes in the path
174
+ #
175
+ # @param path [String] the path to retrieve the file on remote storage
176
+ # @return [String] the new string after removing leading slashed
177
+ def format_path(path)
178
+ path.blank? ? '' : path.gsub(%r{^/+}, '')
179
+ end
180
+
181
+ # A path consis of the bucket name and file name
182
+ # https://docs.aliyun.com/#/pub/oss/api-reference/access-control&signature-header
183
+ #
184
+ # @param path [String] the path to retrieve the file on remote storage
185
+ # @return [String] the expected bucket path, e.g. "test-bucket/oss-api.pdf"
186
+ def get_bucket_path(path)
187
+ [@aliyun_bucket, path].join('/')
188
+ end
189
+
190
+ # The full path contains host name to the file
191
+ #
192
+ # @param path [String] the path to retrieve the file on remote storage
193
+ # @return [String] the expected full path, e.g. "http://martin-test.oss-cn-hangzhou.aliyuncs.com/oss-api.pdf"
194
+ def path_to_url(path)
195
+ url = path =~ %r{^https?://} ? path : "http://#{aliyun_upload_host}/#{path}"
196
+ # URI.encode is deprecated in Ruby 2.7 and removed in Ruby 3.0
197
+ # Use URI::DEFAULT_PARSER.escape for Ruby 3 compatibility
198
+ URI::DEFAULT_PARSER.escape(url)
199
+ end
200
+
201
+ private
202
+
203
+ # The signature algorithm
204
+ # https://docs.aliyun.com/#/pub/oss/api-reference/access-control&signature-header
205
+ #
206
+ # @param verb [String] the request verb, e.g. "GET" or "DELETE"
207
+ # @param content_md5 [String] the md5 value for the content to be uploaded
208
+ # @param content_type [String] the content type of the file, e.g. "application/pdf"
209
+ # @param date [String] the GMT formatted date string
210
+ def sign(verb, path, content_md5, content_type, date)
211
+ canonicalized_oss_headers = ''
212
+ canonicalized_resource = "/#{path}"
213
+ string_to_sign = [
214
+ verb, content_md5, content_type, date,
215
+ canonicalized_oss_headers + canonicalized_resource
216
+ ].join("\n")
217
+ digest = OpenSSL::Digest.new('sha1')
218
+ h = OpenSSL::HMAC.digest(digest, @aliyun_access_key, string_to_sign)
219
+ "OSS #{@aliyun_access_id}:#{Base64.encode64(h)}"
220
+ end
221
+ end
222
+ end
@@ -0,0 +1,43 @@
1
+ require 'aliyun/errors'
2
+
3
+ module Aliyun
4
+ module DataCenter
5
+ # https://help.aliyun.com/document_detail/31837.html
6
+ AVAILABLE_DATA_CENTERS = %w[
7
+ oss-cn-hangzhou
8
+ oss-cn-shanghai
9
+ oss-cn-qingdao
10
+ oss-cn-beijing
11
+ oss-cn-zhangjiakou
12
+ oss-cn-huhehaote
13
+ oss-cn-shenzhen
14
+ oss-cn-heyuan
15
+ oss-cn-chengdu
16
+ oss-cn-hongkong
17
+ oss-us-west-1
18
+ oss-us-east-1
19
+ oss-ap-southeast-1
20
+ oss-ap-southeast-2
21
+ oss-ap-southeast-3
22
+ oss-ap-southeast-5
23
+ oss-ap-northeast-1
24
+ oss-ap-northeast-2
25
+ oss-ap-south-1
26
+ oss-eu-central-1
27
+ oss-eu-west-1
28
+ oss-me-east-1
29
+ ]
30
+
31
+ def get_endpoint(options)
32
+ data_center = options[:data_center]
33
+
34
+ data_center = 'oss-' + data_center unless data_center.match(/^oss/)
35
+
36
+ unless AVAILABLE_DATA_CENTERS.include?(data_center)
37
+ raise InvalildDataCenter, "Unsupported Data Center #{options[:data_center]} Detected"
38
+ end
39
+
40
+ "#{data_center}#{options[:internal] ? '-internal' : ''}.aliyuncs.com"
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,4 @@
1
+ module Aliyun
2
+ class InvalildDataCenter < StandardError
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ require 'paperclip'
2
+
3
+ require 'paperclip/storage/aliyun'
4
+ require 'aliyun/connection'
@@ -0,0 +1,80 @@
1
+ module Paperclip
2
+ module Storage
3
+ module Aliyun
4
+ def self.extended(base)
5
+ base.instance_eval do
6
+ @aliyun_options = @options[:aliyun]
7
+ end
8
+
9
+ [
10
+ :aliyun_upload_url, :aliyun_internal_url,
11
+ :aliyun_external_url, :aliyun_alias_url
12
+ ].each do |url_style|
13
+ Paperclip.interpolates(url_style) do |attachment, style|
14
+ attachment.send(url_style, style)
15
+ end unless Paperclip::Interpolations.respond_to? url_style
16
+ end
17
+ end
18
+
19
+ def build_aliyun_object_url(host, style)
20
+ if oss_connection.aliyun_protocol_relative_url
21
+ "//#{host}/#{path(style).sub(%r{\A/}, '')}"
22
+ else
23
+ "#{oss_connection.aliyun_protocol}://#{host}/#{path(style).sub(%r{\A/}, '')}"
24
+ end
25
+ end
26
+
27
+ def aliyun_upload_url(style = default_style)
28
+ build_aliyun_object_url(oss_connection.aliyun_upload_host, style)
29
+ end
30
+
31
+ def aliyun_internal_url(style = default_style)
32
+ build_aliyun_object_url(oss_connection.aliyun_internal_host, style)
33
+ end
34
+
35
+ def aliyun_external_url(style = default_style)
36
+ build_aliyun_object_url(oss_connection.aliyun_external_host, style)
37
+ end
38
+
39
+ def aliyun_alias_url(style = default_style)
40
+ build_aliyun_object_url(oss_connection.aliyun_alias_host, style)
41
+ end
42
+
43
+ def exists?(style = default_style)
44
+ path(style) ? oss_connection.exists?(path(style)) : false
45
+ end
46
+
47
+ def flush_writes #:nodoc:
48
+ @queued_for_write.each do |style_name, file|
49
+ oss_connection.put path(style_name), (File.new file.path), content_type: file.content_type
50
+ end
51
+
52
+ after_flush_writes
53
+
54
+ @queued_for_write = {}
55
+ end
56
+
57
+ def flush_deletes #:nodoc:
58
+ @queued_for_delete.each do |path|
59
+ oss_connection.delete path
60
+ end
61
+
62
+ @queued_for_delete = []
63
+ end
64
+
65
+ def copy_to_local_file(style, local_dest_path)
66
+ log("copying #{path(style)} to local file #{local_dest_path}")
67
+ local_file = ::File.open(local_dest_path, 'wb')
68
+ remote_file_str = oss_connection.get path(style)
69
+ local_file.write(remote_file_str)
70
+ local_file.close
71
+ end
72
+
73
+ def oss_connection
74
+ @oss_connection ||= ::Aliyun::Connection.new(
75
+ Paperclip::Attachment.default_options[:aliyun].merge(@aliyun_options)
76
+ )
77
+ end
78
+ end
79
+ end
80
+ end
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kt-paperclip-aliyun
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Martin Hong
8
+ - Aidi Stan
9
+ - 7a6163
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2025-12-09 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: kt-paperclip
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - ">="
20
+ - !ruby/object:Gem::Version
21
+ version: 3.5.2
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: 3.5.2
29
+ - !ruby/object:Gem::Dependency
30
+ name: rest-client
31
+ requirement: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: 2.0.0
36
+ type: :runtime
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 2.0.0
43
+ description: Provides Aliyun OSS (Object Storage Service) storage backend for kt-paperclip
44
+ gem
45
+ email: hongzeqin@gmail.com
46
+ executables: []
47
+ extensions: []
48
+ extra_rdoc_files: []
49
+ files:
50
+ - CHANGELOG.md
51
+ - README.md
52
+ - lib/aliyun/connection.rb
53
+ - lib/aliyun/data_center.rb
54
+ - lib/aliyun/errors.rb
55
+ - lib/kt-paperclip-aliyun.rb
56
+ - lib/paperclip/storage/aliyun.rb
57
+ homepage: https://github.com/7a6163/kt-paperclip-aliyun
58
+ licenses:
59
+ - MIT
60
+ metadata:
61
+ bug_tracker_uri: https://github.com/7a6163/kt-paperclip-aliyun/issues
62
+ changelog_uri: https://github.com/7a6163/kt-paperclip-aliyun/blob/master/CHANGELOG.md
63
+ source_code_uri: https://github.com/7a6163/kt-paperclip-aliyun
64
+ post_install_message:
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: 2.7.0
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ requirements: []
79
+ rubygems_version: 3.5.22
80
+ signing_key:
81
+ specification_version: 4
82
+ summary: Aliyun OSS storage adapter for kt-paperclip
83
+ test_files: []