kloudless 0.1.0

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