kloudless 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: aadd0e43b6426a49c8172bbc0e5f5dcae993744c
4
+ data.tar.gz: 69dde9fd66257fa64f3a05e87ab8fa7c88596695
5
+ SHA512:
6
+ metadata.gz: 4c61c7453b87e8e0b8e333d91ecfb8dd1783433a183882764ec13287f9c5e409f30cb06cbcdae9836ff10cf3da786ccf5a00ffc8fdc7ce50cb738b31a9f76b96
7
+ data.tar.gz: c487c4ce1952757c2c84e332c3d6294b6a17f84b51df1b343fba952b329a4b68dc0add13763c45783c097d1200e8ec0beb907b57a6ffcc7b429a044a40cb87a7
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in kloudless.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Jerry Cheung
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,62 @@
1
+ # Kloudless
2
+
3
+ Kloudless API Ruby client.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'kloudless'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install kloudless
18
+
19
+ ## Usage
20
+
21
+ See the [Kloudless API Docs](https://developers.kloudless.com/docs) for the
22
+ official reference. You can obtain an API Key at the [Developer
23
+ Portal](https://developers.kloudless.com/).
24
+
25
+ ```ruby
26
+ # Authentication
27
+ Kloudless.authorize(api_key: "abc")
28
+
29
+ accounts = Kloudless::Account.list
30
+ account = accounts.first
31
+
32
+ account = Kloudless::Account.update(account_id: account.id, active: false)
33
+ Kloudless::Account.delete(account_id: account.id)
34
+ ```
35
+
36
+ ## Version
37
+
38
+ This gem uses [semantic versioning](http://semver.org), where a version number
39
+ looks like:
40
+
41
+ ```
42
+ v major.minor.patch
43
+ ```
44
+
45
+ The major version tracks the version of the Kloudless API. For example, all
46
+ versions that start with `v0.x.y` are compatible with Kloudless API `v0`.
47
+
48
+ ## Release
49
+
50
+ To release this gem, look under the `script` directory. Check out
51
+ [jch/release-scripts](https://github.com/jch/release-scripts) for details.
52
+
53
+ ## Contributing
54
+
55
+ [TODO.md](TODO.md) has a list of things to work on. File an issue or pull
56
+ request if you'd like to discuss or tackle any of those tasks.
57
+
58
+ 1. Fork it ( https://github.com/[my-github-username]/kloudless/fork )
59
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
60
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
61
+ 4. Push to the branch (`git push origin my-new-feature`)
62
+ 5. Create a new [Pull Request](https://help.github.com/send-pull-requests/)
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << "test"
6
+ t.test_files = FileList["test/**/*_test.rb"]
7
+ t.verbose = true
8
+ end
9
+
10
+ task :default => :test
data/TODO.md ADDED
@@ -0,0 +1,26 @@
1
+ # TODO
2
+
3
+ - TravisCI
4
+ - parameter checking
5
+ - README: examples, design, references
6
+ - publish gem
7
+ - rdoc/yard documentation
8
+ - support ruby 2.0? 1.9? (Only thing block this is I use required kwargs from 2.1)
9
+ - what should #delete return? boolean, Response object?
10
+ - associations between models. e.g. Account#files
11
+ - Folder#retrieve should distinguish between subfolders and files
12
+ - webhooks?
13
+ - integration tests that can be run locally
14
+ - File#update not implemented
15
+ - Model attribute setters. e.g. f = File.new; f.name = '3'
16
+ - Object oriented style sugar interface: f = File.new; f.save (does create or update)
17
+ - `find . -type f | xargs grep 'TODO'` for more
18
+
19
+ ## Doc errors
20
+
21
+ - https://developers.kloudless.com/docs#files-list-recent-files takes multiple account ids
22
+ - https://developers.kloudless.com/docs#multipart-upload says files larger than 5MB, but https://developers.kloudless.com/docs#files-upload-a-file says 100MB
23
+ - https://developers.kloudless.com/docs#multipart-upload-initialize-multipart-session is missing required parent_id
24
+ - 'multipart_id' is better variable name? https://developers.kloudless.com/docs#multipart-upload-retrieve-multipart-session-information
25
+ - All other models are create, but Account is import https://developers.kloudless.com/docs#accounts-import-an-account
26
+ - events aren't really a collection. e.g. doesn't have total, count, only cursor
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'kloudless/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "kloudless"
8
+ spec.version = Kloudless::VERSION
9
+ spec.authors = ["Jerry Cheung"]
10
+ spec.email = ["jollyjerry@gmail.com"]
11
+ spec.summary = %q{Kloudless API client}
12
+ spec.homepage = "https://github.com/jch/kloudless"
13
+ spec.license = "MIT"
14
+ spec.required_ruby_version = ">= 2.1"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^test/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.6"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "byebug"
24
+ spec.add_development_dependency "minitest"
25
+ end
@@ -0,0 +1,50 @@
1
+ require_relative "kloudless/collection"
2
+ require_relative "kloudless/error"
3
+ require_relative "kloudless/http"
4
+ require_relative "kloudless/model"
5
+ require_relative "kloudless/account"
6
+ require_relative "kloudless/account_key"
7
+ require_relative "kloudless/team"
8
+ require_relative "kloudless/file"
9
+ require_relative "kloudless/multipart_upload"
10
+ require_relative "kloudless/folder"
11
+ require_relative "kloudless/link"
12
+ require_relative "kloudless/event"
13
+ require_relative "kloudless/version"
14
+ require "json"
15
+ require "uri"
16
+
17
+ module Kloudless
18
+ API_VERSION = "v0".freeze
19
+ API_URL = "https://api.kloudless.com/#{API_VERSION}".freeze
20
+
21
+ # Public: Authorize with API Key or Account Key. Returns nothing.
22
+ #
23
+ # Options:
24
+ # :api_key
25
+ # :account_key
26
+ #
27
+ # https://developers.kloudless.com/docs#authorization
28
+ def self.authorize(options = {})
29
+ Kloudless::HTTP.headers["Authorization"] = if options[:api_key]
30
+ "ApiKey #{options[:api_key]}"
31
+ elsif options[:account_key]
32
+ "AccountKey #{options[:account_key]}"
33
+ else
34
+ raise ArgumentError.new(":api_key or :account_key required")
35
+ end
36
+ end
37
+
38
+ # Internal: HTTP client for easier mocking
39
+ def self.http
40
+ @http || Kloudless::HTTP
41
+ end
42
+
43
+ def self.http=(client)
44
+ @http = client
45
+ end
46
+
47
+ def http
48
+ self.class.http
49
+ end
50
+ end
@@ -0,0 +1,33 @@
1
+ module Kloudless
2
+ # Each account represents a cloud storage account that a user has connected to
3
+ # your app.
4
+ #
5
+ # https://developers.kloudless.com/docs#accounts
6
+ class Account < Model
7
+ # Public: Returns Kloudless::Collection. Raises Kloudless::Error.
8
+ def self.list(params = {})
9
+ Kloudless::Collection.new(self, http.get("/accounts", params: params))
10
+ end
11
+
12
+ def self.retrieve(account_id:, **params)
13
+ new(http.get("/accounts/#{account_id}", params: params))
14
+ end
15
+
16
+ def self.update(account_id:, **params)
17
+ new(http.patch("/accounts/#{account_id}", params: params))
18
+ end
19
+
20
+ def self.delete(account_id:)
21
+ new(http.delete("/accounts/#{account_id}"))
22
+ end
23
+
24
+ # Public: TODO: Returns ???. Raises Kloudless::Error.
25
+ def self.import(params = {})
26
+ http.post("/accounts", params: params)
27
+ end
28
+
29
+ class << self
30
+ alias_method :create, :import
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,26 @@
1
+ module Kloudless
2
+ # Account Keys can be used instead of API Keys to restrict access to a
3
+ # specific account’s data. This is most useful for client-side requests.
4
+ #
5
+ # https://developers.kloudless.com/docs#account-keys
6
+ class AccountKey < Model
7
+ # Public: Returns Kloudless::Collection of AccountKey. Raises
8
+ # Kloudless::Error.
9
+ #
10
+ # :account_ids - Array of account_ids to fetch keys for
11
+ def self.list(account_ids:, **params)
12
+ path = "/accounts/#{account_ids.join(',')}/keys"
13
+ Kloudless::Collection.new(self, http.get(path, params: params))
14
+ end
15
+
16
+ def self.retrieve(account_id:, key_id:, **params)
17
+ path = "/accounts/#{account_id}/keys/#{key_id}"
18
+ new(http.get(path, params: params))
19
+ end
20
+
21
+ def self.delete(account_id:, key_id:)
22
+ path = "/accounts/#{account_id}/keys/#{key_id}"
23
+ new(http.delete(path))
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,32 @@
1
+ module Kloudless
2
+ class Collection
3
+ include Enumerable
4
+
5
+ def initialize(model, json)
6
+ @model = model
7
+ @json = json
8
+ end
9
+
10
+ def total
11
+ @json["total"]
12
+ end
13
+
14
+ def count
15
+ @json["count"]
16
+ end
17
+
18
+ def page
19
+ @json["page"]
20
+ end
21
+
22
+ def objects
23
+ @json["objects"]
24
+ end
25
+
26
+ def each
27
+ @json["objects"].each do |attrs|
28
+ yield @model.new(attrs)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,172 @@
1
+ module Kloudless
2
+ # All errors inherit from Kloudless::Error. You may rescue this class as a
3
+ # catch all. Specific errors are documented in
4
+ # https://developers.kloudless.com/docs#errors
5
+ #
6
+ # begin
7
+ # # ... some api action
8
+ # rescue Kloudless::Error => e
9
+ # $stderr.puts e.status_code # 401
10
+ # $stderr.puts e.error_code # 'unauthorized'
11
+ # $stderr.puts e.id # id of request that caused the error
12
+ # $stderr.puts e.conflicting_resource_id
13
+ # $stderr.puts e.message
14
+ # end
15
+ #
16
+ class Error < RuntimeError
17
+ # Public: e.g. 400, 401, 500, 4xx-5xx.
18
+ attr_accessor :status_code
19
+
20
+ # Public: short string name of error. e.g. bad_request, request_failed
21
+ attr_accessor :error_code
22
+
23
+ # Public: identifier of request that caused the error
24
+ attr_accessor :id
25
+
26
+ # Public: identitifer of resource request conflicted with. See 409.
27
+ attr_accessor :conflicting_resource_id
28
+
29
+ # Internal: `json` is a Hash. Returns an instantiated subclass of
30
+ # Kloudless::Error.
31
+ def self.from_json(json)
32
+ error_class = ERRORS.fetch(json["error_code"].to_sym, UnknownError)
33
+ error = error_class.new(json["message"])
34
+ error.error_code = json["error_code"]
35
+ error.status_code = json["status_code"]
36
+ error.id = json["id"]
37
+ error.conflicting_resource_id = json["conflicting_resource_id"]
38
+ error
39
+ end
40
+ end
41
+
42
+ # 400 The request is malformed. Check the message for the reason.
43
+ class BadRequestError < Error; end
44
+
45
+ # 400 The request failed. Check the message for the reason.
46
+ class RequestFailedError < Error; end
47
+
48
+ # 400 Invalid parent folder_id parameter.
49
+ class InvalidParentFolderError < Error; end
50
+
51
+ # 400 The request is malformed: the parameters are incorrect.
52
+ class InvalidParametersError < Error; end
53
+
54
+ # 400 The resource_type provided is not correct.
55
+ class InvalidResourceTypeError < Error; end
56
+
57
+ # 401 The Kloudless credentials or token provided are invalid.
58
+ class UnauthorizedError < Error; end
59
+
60
+ # 401 The credentials or token provided for the upstream cloud storage
61
+ # service are invalid.
62
+ class ServiceUnauthorizedError < Error; end
63
+
64
+ # 401 An authorization parameter is missing.
65
+ class AuthenticationRequiredError < Error; end
66
+
67
+ # 401 Invalid request token provided.
68
+ class InvalidTokenError < Error; end
69
+
70
+ # 403 Cannot delete the folder as it is not empty and the recursive parameter is not set to True.
71
+ class FolderNotEmptyError < Error; end
72
+
73
+ # 403 Unable to access resource.
74
+ class PermissionDeniedError < Error; end
75
+
76
+ # 403 Forbidden request made to Kloudless.
77
+ class ForbiddenError < Error; end
78
+
79
+ # 403 Forbidden request made to the upstream cloud storage service.
80
+ class ServiceForbiddenError < Error; end
81
+
82
+ # 404 The requested resource was not found.
83
+ class NotFoundError < Error; end
84
+
85
+ # 405 The request was made using an unsupported method.
86
+ class MethodNotAllowedError < Error; end
87
+
88
+ # 406 The request was made with unacceptable data.
89
+ class NotAcceptableError < Error; end
90
+
91
+ # 409 Resource already exists. Checking the conflicting_resource_id
92
+ # parameter in the response.
93
+ class NamingConflictError < Error; end
94
+
95
+ # 429 Too many requests to the Kloudless API were made. Check the
96
+ # Retry-After header.
97
+ class TooManyRequestsError < Error; end
98
+
99
+ # 429 Too many requests to the underlying service API were made. Check the Retry-After header.
100
+ class TooManyServiceRequestsError < Error; end
101
+
102
+ # 500 The request was made by Kloudless with an unsupported method.*
103
+ class MethodNotAllowedError < Error; end
104
+
105
+ # 500 The request was made by Kloudless with unacceptable data.*
106
+ class NotAcceptableError < Error; end
107
+
108
+ # 500 Kloudless experienced an internal error.*
109
+ class InternalErrorError < Error; end
110
+
111
+ # 500 Kloudless experienced an error retrieving link data.*
112
+ class LinkErrorError < Error; end
113
+
114
+ # 500 Kloudless experienced an error making an invalid request.*
115
+ class UnsupportedMediaTypeError < Error; end
116
+
117
+ # 501 The request method is not currently implemented for the cloud storage service.
118
+ class NotImplementedError < Error; end
119
+
120
+ # 502 Kloudless received an invalid response from the upstream cloud storage service.
121
+ class BadGatewayError < Error; end
122
+
123
+ # 503 The upstream cloud storage service is unavailable.
124
+ class ServiceNotAvailableError < Error; end
125
+
126
+ # 504 The upstream cloud storage service is unavailable.
127
+ class GatewayTimeoutError < Error; end
128
+
129
+ # 507 There is not enough free space in the cloud storage account to complete the request.
130
+ class InsufficientStorageError < Error; end
131
+
132
+ # We should never see this error. This means the API added an error we don't
133
+ # know about.
134
+ class UnknownError < Error; end
135
+
136
+ class Error
137
+ # We could use ActiveSupport::Inflector here, but didn't want to bring in a
138
+ # dependency. To regenerate, copy from docs, and run:
139
+ #
140
+ # pbpaste | ruby -r'active_support/core_ext/string/inflections' -ne 'puts "#{$_.chomp}: #{$_.classify.chomp},"'
141
+ ERRORS = {
142
+ bad_request: BadRequestError,
143
+ request_failed: RequestFailedError,
144
+ invalid_parent_folder: InvalidParentFolderError,
145
+ invalid_parameters: InvalidParametersError,
146
+ invalid_resource_type: InvalidResourceTypeError,
147
+ unauthorized: UnauthorizedError,
148
+ service_unauthorized: ServiceUnauthorizedError,
149
+ authentication_required: AuthenticationRequiredError,
150
+ invalid_token: InvalidTokenError,
151
+ folder_not_empty: FolderNotEmptyError,
152
+ permission_denied: PermissionDeniedError,
153
+ forbidden: ForbiddenError,
154
+ service_forbidden: ServiceForbiddenError,
155
+ not_found: NotFoundError,
156
+ method_not_allowed: MethodNotAllowedError,
157
+ not_acceptable: NotAcceptableError,
158
+ naming_conflict: NamingConflictError,
159
+ too_many_requests: TooManyRequestsError,
160
+ too_many_service_requests: TooManyServiceRequestsError,
161
+ method_not_allowed: MethodNotAllowedError,
162
+ not_acceptable: NotAcceptableError,
163
+ internal_error: InternalErrorError,
164
+ link_error: LinkErrorError,
165
+ unsupported_media_type: UnsupportedMediaTypeError,
166
+ not_implemented: NotImplementedError,
167
+ bad_gateway: BadGatewayError,
168
+ service_not_available: ServiceNotAvailableError,
169
+ gateway_timeout: GatewayTimeoutError,
170
+ insufficient_storage: InsufficientStorageError }
171
+ end
172
+ end