versacommerce-theme_api_client 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +21 -0
- data/README.md +229 -0
- data/Rakefile +1 -0
- data/bin/console +7 -0
- data/bin/setup +7 -0
- data/lib/versacommerce/theme_api_client/client.rb +61 -0
- data/lib/versacommerce/theme_api_client/fetcher.rb +101 -0
- data/lib/versacommerce/theme_api_client/relation.rb +68 -0
- data/lib/versacommerce/theme_api_client/relations/directory_relation.rb +13 -0
- data/lib/versacommerce/theme_api_client/relations/file_relation.rb +13 -0
- data/lib/versacommerce/theme_api_client/resource.rb +60 -0
- data/lib/versacommerce/theme_api_client/resources/directory.rb +104 -0
- data/lib/versacommerce/theme_api_client/resources/file.rb +135 -0
- data/lib/versacommerce/theme_api_client/resources/file_behaviour.rb +36 -0
- data/lib/versacommerce/theme_api_client/version.rb +5 -0
- data/lib/versacommerce/theme_api_client.rb +61 -0
- data/versacommerce-theme_api_client.gemspec +29 -0
- metadata +161 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 990ff9457b19aff2773db59aa619d9a7a96879c6
|
4
|
+
data.tar.gz: 5a23a121d1f16b738575552839d33fab9f98197e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ea312975266e5522796eec6c7ff332627678e0b928c7651a8ffc51526b58b88a1a54c17c7d80429b56a3518fec70b24bc54050119d147209567b10061870cce5
|
7
|
+
data.tar.gz: 0d04b8c38e6a91098c97dabacc951b6fa6740f4b29b1b6cb451fb02ed3916ac87209ba2781f3550f8cfe23af990c69cf334f7eb1c6a669fe7df0853ecc782e7f
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 VersaCommerce
|
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,229 @@
|
|
1
|
+
# Versacommerce::ThemeAPIClient
|
2
|
+
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/versacommerce-theme_api_client.svg)](http://badge.fury.io/rb/versacommerce-theme_api_client)
|
4
|
+
|
5
|
+
Versacommerce::ThemeAPIClient is a library to consume the VersaCommerce Theme API.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'versacommerce-theme_api_client'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
```sh
|
18
|
+
$ bundle install
|
19
|
+
```
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
```sh
|
24
|
+
$ gem install versacommerce-theme_api_client
|
25
|
+
```
|
26
|
+
|
27
|
+
## Usage
|
28
|
+
|
29
|
+
Create a client object to work on:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
authorization = 'YOUR_AUTHORIZATION'
|
33
|
+
client = Versacommerce::ThemeAPIClient.new(authorization: authorization)
|
34
|
+
```
|
35
|
+
|
36
|
+
The client object is tied to a single theme, depending on the authorization. You can get an authorization from your shop's admin section.
|
37
|
+
|
38
|
+
### Working with Directories
|
39
|
+
|
40
|
+
#### Finding Directories
|
41
|
+
|
42
|
+
Calling `#directories` on the client object will get you a `DirectoryRelation` object that responds to `#each`. As it includes the `Enumerable` module, the following will get you an array of all directories:
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
directories = client.directories.to_a
|
46
|
+
```
|
47
|
+
|
48
|
+
If you want to get an array of a directory's child directories, call:
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
directories = client.directories.in_path('path/to/directory').to_a
|
52
|
+
```
|
53
|
+
|
54
|
+
This is practically the same as calling:
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
directories = client.directories(path: 'path/to/directory')
|
58
|
+
```
|
59
|
+
|
60
|
+
These calls are recursive and will return all directory children by default. If you don't want this behaviour and instead only get direct child directories, turn it off by calling:
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
directories = client.directories(recursive: false)
|
64
|
+
|
65
|
+
# respectively:
|
66
|
+
directories = client.directories.in_path('', recursive: false)
|
67
|
+
```
|
68
|
+
|
69
|
+
#### Finding a single Directory
|
70
|
+
|
71
|
+
If you want to find a single directory, call:
|
72
|
+
```ruby
|
73
|
+
directory = client.directories.find('path/to/directory')
|
74
|
+
```
|
75
|
+
|
76
|
+
Nesting with the relation is also possible. The following will try to find the directory `assets/font`:
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
directory = client.directories(path: 'assets').find('font')
|
80
|
+
```
|
81
|
+
|
82
|
+
If the directory could be found, a `Directory` instance is returned, else a `RecordNotFoundError` is raised. A `Directory` instance has a `path` instance variable representing its path and responds to `#files`, which returns a `FileRelation` with the directory's path as base for the files.
|
83
|
+
|
84
|
+
#### Creating a Directory
|
85
|
+
|
86
|
+
In order to create a directory, use `#build` in combination with `#save` or use `#create`:
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
directory = client.directories.build(path: 'special_font')
|
90
|
+
directory.save # => true or false
|
91
|
+
|
92
|
+
# which is basically the same as:
|
93
|
+
directory = client.directories(path: 'assets/font').create(path: 'special_fonts')
|
94
|
+
```
|
95
|
+
|
96
|
+
If the directory could not be saved, `false` will be returned and the `directory.errors` object populated with the errors that occured.
|
97
|
+
|
98
|
+
#### Updating a Directory
|
99
|
+
|
100
|
+
Updating a directory is similar to creating one:
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
directory.update(path: 'new/path')
|
104
|
+
|
105
|
+
# which is basically the same as:
|
106
|
+
directory.path = 'new/path'
|
107
|
+
directory.save
|
108
|
+
```
|
109
|
+
|
110
|
+
#### Deleting a Directory
|
111
|
+
|
112
|
+
Delete a directory by calling:
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
directory.delete
|
116
|
+
```
|
117
|
+
|
118
|
+
If you don't want to prefetch a directory in order to delete it, there's also:
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
client.directories.delete('path/to/directory')
|
122
|
+
```
|
123
|
+
|
124
|
+
### Working with Files
|
125
|
+
|
126
|
+
#### Finding Files
|
127
|
+
|
128
|
+
Working with files is like dealing with directories. You can retrieve all files by calling:
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
files = client.files.to_a
|
132
|
+
```
|
133
|
+
|
134
|
+
This will return an array of all files. A significant difference between a directory and a file is that a file can have content. Finding files using the method described above won't load the files' contents though. Reloading the content is possible:
|
135
|
+
|
136
|
+
```ruby
|
137
|
+
file = client.files.to_a.first
|
138
|
+
file.content # => nil
|
139
|
+
|
140
|
+
file.reload_content
|
141
|
+
file.content # => returns the content as String
|
142
|
+
```
|
143
|
+
|
144
|
+
Filtering files works like with a directory.
|
145
|
+
|
146
|
+
```ruby
|
147
|
+
files = client.files(path: 'assets').to_a
|
148
|
+
|
149
|
+
# or
|
150
|
+
files = client.files.in_path('assets').to_a
|
151
|
+
```
|
152
|
+
|
153
|
+
will return all files in (or below) the `assets` directory.
|
154
|
+
|
155
|
+
#### Finding a single File
|
156
|
+
|
157
|
+
```ruby
|
158
|
+
file = client.files(path: 'assets').find('fonts/some_font.svg')
|
159
|
+
```
|
160
|
+
|
161
|
+
will find the file located at `assets/fonts/some_font.svg`. As this will be a text file, the content will be loaded automatically. If the file is an image (or any other file not in text format) the content will not be loaded. If you want to make sure the content is loaded, provide a `load_content` keyword argument:
|
162
|
+
|
163
|
+
```ruby
|
164
|
+
file = client.files.find('assets/images/banner.png', load_content: true)
|
165
|
+
```
|
166
|
+
|
167
|
+
or load the content afterwards manually if it is missing:
|
168
|
+
|
169
|
+
```ruby
|
170
|
+
file = client.files.find('assets/images/banner.png')
|
171
|
+
file.reload_content unless file.has_content?
|
172
|
+
```
|
173
|
+
|
174
|
+
#### Creating a File
|
175
|
+
|
176
|
+
In order to create a file, use `#build` in combination with `#save` or use `#create`:
|
177
|
+
|
178
|
+
```ruby
|
179
|
+
file = client.directories.build(path: 'assets/fonts/some_font.svg', content: 'content here')
|
180
|
+
file.save # => true or false
|
181
|
+
|
182
|
+
# which is basically the same as:
|
183
|
+
file = client.directories.create(path: 'assets/font', content: 'content here')
|
184
|
+
```
|
185
|
+
|
186
|
+
If the file could not be saved, `false` will be returned and the `file.errors` object populated with the errors that occured.
|
187
|
+
|
188
|
+
#### Updating a File
|
189
|
+
|
190
|
+
Updating a file is similar to creating one:
|
191
|
+
|
192
|
+
```ruby
|
193
|
+
file.update(path: 'new/path', content: 'new content')
|
194
|
+
|
195
|
+
# which is basically the same as:
|
196
|
+
file.path = 'new/path'
|
197
|
+
file.content = 'new content'
|
198
|
+
file.save
|
199
|
+
```
|
200
|
+
|
201
|
+
#### Deleting a file
|
202
|
+
|
203
|
+
Delete a file by calling:
|
204
|
+
|
205
|
+
```ruby
|
206
|
+
file.delete
|
207
|
+
```
|
208
|
+
|
209
|
+
If you don't want to prefetch a file in order to delete it, there's also:
|
210
|
+
|
211
|
+
```ruby
|
212
|
+
client.files.delete('path/to/file')
|
213
|
+
```
|
214
|
+
|
215
|
+
## Development
|
216
|
+
|
217
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
|
218
|
+
|
219
|
+
## Contributing
|
220
|
+
|
221
|
+
1. Fork it (https://github.com/versacommerce/versacommerce-theme_api_client/fork)
|
222
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
223
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
224
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
225
|
+
5. Create a new Pull Request
|
226
|
+
|
227
|
+
## License
|
228
|
+
|
229
|
+
[MIT](https://github.com/versacommerce/versacommerce-theme_api_client/blob/master/LICENSE.txt "MIT License").
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
data/bin/console
ADDED
data/bin/setup
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'theme_api_client/fetcher'
|
2
|
+
require 'theme_api_client/resources/directory'
|
3
|
+
require 'theme_api_client/relations/directory_relation'
|
4
|
+
require 'theme_api_client/resources/file'
|
5
|
+
require 'theme_api_client/relations/file_relation'
|
6
|
+
require 'theme_api_client/version'
|
7
|
+
|
8
|
+
module Versacommerce
|
9
|
+
class ThemeAPIClient
|
10
|
+
attr_accessor :authorization
|
11
|
+
attr_writer :base_url, :fetcher
|
12
|
+
|
13
|
+
def initialize(attributes = {})
|
14
|
+
attributes.each do |key, value|
|
15
|
+
public_send("#{key}=", value)
|
16
|
+
end
|
17
|
+
|
18
|
+
yield(self) if block_given?
|
19
|
+
end
|
20
|
+
|
21
|
+
def base_url
|
22
|
+
@base_url || 'https://theme-api.versacommerce.de'
|
23
|
+
end
|
24
|
+
|
25
|
+
def directories(path: '', recursive: true)
|
26
|
+
Relations::DirectoryRelation.new(self, path: path, recursive: recursive)
|
27
|
+
end
|
28
|
+
|
29
|
+
def directory_class
|
30
|
+
@directory_class ||= Class.new(Resources::Directory).tap { |dir| dir.client = self }
|
31
|
+
end
|
32
|
+
|
33
|
+
def files(path: '', recursive: true)
|
34
|
+
Relations::FileRelation.new(self, path: path, recursive: recursive)
|
35
|
+
end
|
36
|
+
|
37
|
+
def file_class
|
38
|
+
@file_class ||= Class.new(Resources::File).tap { |dir| dir.client = self }
|
39
|
+
end
|
40
|
+
|
41
|
+
def fetcher
|
42
|
+
@fetcher ||= Fetcher.new(self)
|
43
|
+
end
|
44
|
+
|
45
|
+
def directory_path(path = '')
|
46
|
+
Pathname.new('directories').join(path)
|
47
|
+
end
|
48
|
+
|
49
|
+
def file_path(path = '')
|
50
|
+
Pathname.new('files').join(path)
|
51
|
+
end
|
52
|
+
|
53
|
+
def download_path(path = '')
|
54
|
+
Pathname.new('download').join(path)
|
55
|
+
end
|
56
|
+
|
57
|
+
def tree_path(path = '')
|
58
|
+
Pathname.new('tree').join(path)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'http'
|
3
|
+
require 'active_support/core_ext/module/delegation'
|
4
|
+
|
5
|
+
module Versacommerce
|
6
|
+
class ThemeAPIClient
|
7
|
+
class Fetcher
|
8
|
+
RecordNotFoundError = Class.new(StandardError)
|
9
|
+
UnauthorizedError = Class.new(StandardError)
|
10
|
+
|
11
|
+
delegate :authorization, to: :client
|
12
|
+
|
13
|
+
attr_reader :client
|
14
|
+
|
15
|
+
def initialize(client)
|
16
|
+
@client = client
|
17
|
+
end
|
18
|
+
|
19
|
+
def get(path)
|
20
|
+
url = url_for_path(path)
|
21
|
+
response = with_headers.get(url)
|
22
|
+
handle_response(response, url)
|
23
|
+
end
|
24
|
+
|
25
|
+
def head(path)
|
26
|
+
url = url_for_path(path)
|
27
|
+
response = with_headers.head(url)
|
28
|
+
handle_response(response, url)
|
29
|
+
end
|
30
|
+
|
31
|
+
def post(path, json = {})
|
32
|
+
url = url_for_path(path)
|
33
|
+
response = with_headers.post(url_for_path(path), json: json)
|
34
|
+
handle_response(response, url)
|
35
|
+
end
|
36
|
+
|
37
|
+
def post_form(path, form_data = {})
|
38
|
+
url = url_for_path(path)
|
39
|
+
response = with_headers.post(url_for_path(path), form: form_data)
|
40
|
+
handle_response(response, url)
|
41
|
+
end
|
42
|
+
|
43
|
+
def patch(path, json = {})
|
44
|
+
url = url_for_path(path)
|
45
|
+
response = with_headers.patch(url_for_path(path), json: json)
|
46
|
+
handle_response(response, url)
|
47
|
+
end
|
48
|
+
|
49
|
+
def patch_form(path, form_data = {})
|
50
|
+
url = url_for_path(path)
|
51
|
+
response = with_headers.patch(url_for_path(path), form: form_data)
|
52
|
+
handle_response(response, url)
|
53
|
+
end
|
54
|
+
|
55
|
+
def delete(path = {})
|
56
|
+
url = url_for_path(path)
|
57
|
+
response = with_headers.delete(url)
|
58
|
+
handle_response(response, url)
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def handle_response(response, url)
|
64
|
+
case response.status
|
65
|
+
when ->(s) { s.unauthorized? }
|
66
|
+
handle_unauthorized(response)
|
67
|
+
when ->(s) { s.not_found? }
|
68
|
+
raise RecordNotFoundError.new('Record for URL %s could not be found.' % url)
|
69
|
+
else
|
70
|
+
response
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def handle_unauthorized(response)
|
75
|
+
if response.json.key?('error')
|
76
|
+
raise UnauthorizedError.new('Could not be authorized: %s' % response.json['error'])
|
77
|
+
else
|
78
|
+
raise UnauthorizedError.new('You could not be authorized.')
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def url_for_path(path)
|
83
|
+
'%s/%s' % [client.base_url, path.to_s]
|
84
|
+
end
|
85
|
+
|
86
|
+
def with_headers
|
87
|
+
HTTP.with_headers(accept: 'application/json', 'Theme-Authorization' => authorization)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
class HTTP::Response
|
94
|
+
def json
|
95
|
+
@json ||= if mime_type == 'application/json'
|
96
|
+
JSON.parse(to_s)
|
97
|
+
else
|
98
|
+
{}
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Versacommerce
|
2
|
+
class ThemeAPIClient
|
3
|
+
class Relation
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
attr_accessor :recursive
|
7
|
+
attr_reader :client, :path
|
8
|
+
|
9
|
+
def initialize(client, attributes = {})
|
10
|
+
@client = client
|
11
|
+
|
12
|
+
attributes.each do |key, value|
|
13
|
+
public_send("#{key}=", value)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def find(path, **options)
|
18
|
+
resource_class.find(combined_path(path), **options)
|
19
|
+
end
|
20
|
+
|
21
|
+
def in_path(path, **options)
|
22
|
+
tap do |relation|
|
23
|
+
relation.path = path
|
24
|
+
relation.recursive = options[:recursive] if options.key?(:recursive)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def build(attributes = {})
|
29
|
+
path = combined_path(attributes[:path])
|
30
|
+
resource_class.new(attributes.merge(path: path))
|
31
|
+
end
|
32
|
+
|
33
|
+
def create(attributes = {})
|
34
|
+
build(attributes).tap do |object|
|
35
|
+
object.save
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def delete(path)
|
40
|
+
resource_class.delete(combined_path(path))
|
41
|
+
end
|
42
|
+
|
43
|
+
def each(&block)
|
44
|
+
files = resource_class.in_path(path, recursive)
|
45
|
+
files.each(&block)
|
46
|
+
end
|
47
|
+
|
48
|
+
def path=(value)
|
49
|
+
@path = case value
|
50
|
+
when Pathname
|
51
|
+
value
|
52
|
+
else
|
53
|
+
Pathname.new(value.gsub(/\A\/*/, ''))
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def inspect
|
58
|
+
to_s
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def combined_path(path)
|
64
|
+
self.path.join(path || '')
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
require 'active_support/core_ext/module/delegation'
|
3
|
+
|
4
|
+
module Versacommerce
|
5
|
+
class ThemeAPIClient
|
6
|
+
class Resource
|
7
|
+
include ActiveModel::Model
|
8
|
+
include ActiveModel::Dirty
|
9
|
+
|
10
|
+
class << self
|
11
|
+
attr_accessor :client
|
12
|
+
delegate :fetcher, to: :client
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_writer :new_record
|
16
|
+
|
17
|
+
def initialize(attributes = {})
|
18
|
+
assign_attributes(attributes)
|
19
|
+
clear_changes_information unless new_record?
|
20
|
+
end
|
21
|
+
|
22
|
+
def assign_attributes(attributes = {})
|
23
|
+
attributes.each do |key, value|
|
24
|
+
public_send("#{key}=", value)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def save
|
29
|
+
if valid?(current_context) && commit
|
30
|
+
self.new_record = false
|
31
|
+
changes_applied
|
32
|
+
true
|
33
|
+
else
|
34
|
+
false
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def update(attributes = {})
|
39
|
+
assign_attributes(attributes)
|
40
|
+
save
|
41
|
+
end
|
42
|
+
|
43
|
+
def new_record?
|
44
|
+
defined?(@new_record) ? @new_record : true
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def add_errors(errors)
|
50
|
+
errors.each do |key, errors|
|
51
|
+
errors.each { |error| self.errors[key] << error }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def current_context
|
56
|
+
new_record? ? :create : :update
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'active_support/core_ext/module/delegation'
|
2
|
+
require 'versacommerce/theme_api_client/fetcher'
|
3
|
+
require 'versacommerce/theme_api_client/resources/file_behaviour'
|
4
|
+
require 'versacommerce/theme_api_client/relations/file_relation'
|
5
|
+
require 'versacommerce/theme_api_client/resource'
|
6
|
+
|
7
|
+
module Versacommerce
|
8
|
+
class ThemeAPIClient
|
9
|
+
module Resources
|
10
|
+
class Directory < Resource
|
11
|
+
include FileBehaviour
|
12
|
+
|
13
|
+
delegate :client, :fetcher, :directory_path, to: :class
|
14
|
+
|
15
|
+
class << self
|
16
|
+
delegate :fetcher, :directory_path, :tree_path, to: :client
|
17
|
+
|
18
|
+
def in_path(path = '', recursive = true)
|
19
|
+
recursive ? fetch_recursive(path) : fetch_non_recursive(path)
|
20
|
+
end
|
21
|
+
|
22
|
+
def find(path, **options)
|
23
|
+
fetcher.head(directory_path(path))
|
24
|
+
new(path: path, new_record: false)
|
25
|
+
end
|
26
|
+
|
27
|
+
def delete(path)
|
28
|
+
fetcher.delete(directory_path(path))
|
29
|
+
clear_changes_information
|
30
|
+
self.new_record = true
|
31
|
+
end
|
32
|
+
|
33
|
+
def model_name
|
34
|
+
ActiveModel::Name.new(self, nil, 'Directory')
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def fetch_recursive(path)
|
40
|
+
directory = fetcher.get(tree_path(path)).json
|
41
|
+
directories_from_tree(directory)
|
42
|
+
end
|
43
|
+
|
44
|
+
def fetch_non_recursive(path)
|
45
|
+
fetcher.get(directory_path(path)).json['directories'].map do |directory|
|
46
|
+
new(directory.merge(new_record: false))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def directories_from_tree(directory, directories = [])
|
51
|
+
directories.tap do |dirs|
|
52
|
+
directory.each do |child|
|
53
|
+
if child['type'] == 'directory'
|
54
|
+
dirs << new(child.slice('path').merge(new_record: false))
|
55
|
+
directories_from_tree(child['children'], dirs)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def directories(recursive: false)
|
63
|
+
client.directories(path: path, recursive: recursive)
|
64
|
+
end
|
65
|
+
|
66
|
+
def files(recursive: false)
|
67
|
+
client.files(path: path, recursive: recursive)
|
68
|
+
end
|
69
|
+
|
70
|
+
def delete
|
71
|
+
unless new_record?
|
72
|
+
response = fetcher.delete(directory_path(path))
|
73
|
+
response.status.no_content?
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def inspect
|
78
|
+
'#<Directory:0x%x>' % (object_id << 1)
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def commit
|
84
|
+
response = if new_record?
|
85
|
+
fetcher.post(directory_path, serialized_attributes)
|
86
|
+
else
|
87
|
+
fetcher.patch(directory_path(path_was), serialized_attributes)
|
88
|
+
end
|
89
|
+
|
90
|
+
if response.status.unprocessable_entity?
|
91
|
+
add_errors(response.json['errors']) if response.json.key?('errors')
|
92
|
+
false
|
93
|
+
else
|
94
|
+
true
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def serialized_attributes
|
99
|
+
{directory: {path: path.to_s}}
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'active_support/core_ext/module/delegation'
|
2
|
+
require 'versacommerce/theme_api_client/fetcher'
|
3
|
+
require 'versacommerce/theme_api_client/resources/file_behaviour'
|
4
|
+
require 'versacommerce/theme_api_client/resource'
|
5
|
+
|
6
|
+
module Versacommerce
|
7
|
+
class ThemeAPIClient
|
8
|
+
module Resources
|
9
|
+
class File < Resource
|
10
|
+
include FileBehaviour
|
11
|
+
|
12
|
+
delegate :client, :fetcher, :file_path, :download_path, to: :class
|
13
|
+
define_attribute_methods :content
|
14
|
+
|
15
|
+
class << self
|
16
|
+
delegate :fetcher, :file_path, :tree_path, :download_path, to: :client
|
17
|
+
|
18
|
+
def in_path(path = '', recursive = true)
|
19
|
+
recursive ? fetch_recursive(path) : fetch_non_recursive(path)
|
20
|
+
end
|
21
|
+
|
22
|
+
def find(path, load_content: false)
|
23
|
+
response = fetcher.get(file_path(path))
|
24
|
+
attributes = response.json
|
25
|
+
|
26
|
+
new(attributes.merge(new_record: false)).tap do |file|
|
27
|
+
file.reload_content if load_content && !file.has_content?
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def delete(path)
|
32
|
+
fetcher.delete(file_path(path))
|
33
|
+
true
|
34
|
+
end
|
35
|
+
|
36
|
+
def model_name
|
37
|
+
ActiveModel::Name.new(self, nil, 'File')
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def fetch_recursive(path)
|
43
|
+
directory = fetcher.get(tree_path(path)).json
|
44
|
+
files_from_tree(directory)
|
45
|
+
end
|
46
|
+
|
47
|
+
def fetch_non_recursive(path)
|
48
|
+
directory = fetcher.get(tree_path(path)).json
|
49
|
+
|
50
|
+
directory.map do |child|
|
51
|
+
if child['type'] == 'file'
|
52
|
+
new(child.slice('path', 'stats').merge(new_record: false))
|
53
|
+
end
|
54
|
+
end.compact
|
55
|
+
end
|
56
|
+
|
57
|
+
def files_from_tree(directory, files = [])
|
58
|
+
files.tap do |f|
|
59
|
+
directory.each do |child|
|
60
|
+
case child['type']
|
61
|
+
when 'directory'
|
62
|
+
files_from_tree(child['children'], f)
|
63
|
+
when 'file'
|
64
|
+
f << new(child.slice('path', 'content').merge(new_record: false))
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
attr_reader :content
|
72
|
+
attr_accessor :stats
|
73
|
+
|
74
|
+
def delete
|
75
|
+
unless new_record?
|
76
|
+
response = fetcher.delete(file_path(path))
|
77
|
+
content_will_change!
|
78
|
+
self.new_record = true
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def content=(value)
|
83
|
+
unless value == @content
|
84
|
+
content_will_change!
|
85
|
+
@content = value.to_s
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def has_content?
|
90
|
+
!!@content
|
91
|
+
end
|
92
|
+
|
93
|
+
def reload_content
|
94
|
+
unless new_record?
|
95
|
+
response = fetcher.get(download_path(path))
|
96
|
+
self.content = response.to_s
|
97
|
+
clear_attribute_changes(:content)
|
98
|
+
end
|
99
|
+
|
100
|
+
self
|
101
|
+
end
|
102
|
+
|
103
|
+
def inspect
|
104
|
+
'#<File:0x%x>' % (object_id << 1)
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def commit
|
110
|
+
response = if new_record?
|
111
|
+
fetcher.post_form(file_path, serialized_attributes)
|
112
|
+
else
|
113
|
+
fetcher.patch_form(file_path(path_was), serialized_attributes)
|
114
|
+
end
|
115
|
+
|
116
|
+
if response.status.unprocessable_entity?
|
117
|
+
add_errors(response.json['errors']) if response.json.key?('errors')
|
118
|
+
false
|
119
|
+
else
|
120
|
+
true
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def serialized_attributes
|
125
|
+
if has_content? && content_changed?
|
126
|
+
content_file = HTTP::FormData::File.new(StringIO.new(content), filename: name)
|
127
|
+
{'file[path]' => path.to_s, 'file[content]' => content_file}
|
128
|
+
else
|
129
|
+
{'file[path]' => path.to_s}
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module Versacommerce
|
4
|
+
class ThemeAPIClient
|
5
|
+
module Resources
|
6
|
+
module FileBehaviour
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
define_attribute_method :path
|
11
|
+
attr_reader :path
|
12
|
+
|
13
|
+
validates :path, presence: true
|
14
|
+
validates :name, length: {in: 3..64}, if: proc { name.present? }
|
15
|
+
end
|
16
|
+
|
17
|
+
def path=(value)
|
18
|
+
path = Pathname.new(value.sub(/\A\/*/, ''))
|
19
|
+
|
20
|
+
unless path == @path
|
21
|
+
path_will_change!
|
22
|
+
@path = path
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def name
|
27
|
+
path.basename.to_s
|
28
|
+
end
|
29
|
+
|
30
|
+
def ==(other)
|
31
|
+
other.kind_of?(self.class) && path == other.path
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'versacommerce/theme_api_client/fetcher'
|
2
|
+
require 'versacommerce/theme_api_client/resources/directory'
|
3
|
+
require 'versacommerce/theme_api_client/relations/directory_relation'
|
4
|
+
require 'versacommerce/theme_api_client/resources/file'
|
5
|
+
require 'versacommerce/theme_api_client/relations/file_relation'
|
6
|
+
require 'versacommerce/theme_api_client/version'
|
7
|
+
|
8
|
+
module Versacommerce
|
9
|
+
class ThemeAPIClient
|
10
|
+
attr_accessor :authorization
|
11
|
+
attr_writer :base_url, :fetcher
|
12
|
+
|
13
|
+
def initialize(attributes = {})
|
14
|
+
attributes.each do |key, value|
|
15
|
+
public_send("#{key}=", value)
|
16
|
+
end
|
17
|
+
|
18
|
+
yield(self) if block_given?
|
19
|
+
end
|
20
|
+
|
21
|
+
def base_url
|
22
|
+
@base_url || 'https://theme-api.versacommerce.de'
|
23
|
+
end
|
24
|
+
|
25
|
+
def directories(path: '', recursive: true)
|
26
|
+
Relations::DirectoryRelation.new(self, path: path, recursive: recursive)
|
27
|
+
end
|
28
|
+
|
29
|
+
def directory_class
|
30
|
+
@directory_class ||= Class.new(Resources::Directory).tap { |dir| dir.client = self }
|
31
|
+
end
|
32
|
+
|
33
|
+
def files(path: '', recursive: true)
|
34
|
+
Relations::FileRelation.new(self, path: path, recursive: recursive)
|
35
|
+
end
|
36
|
+
|
37
|
+
def file_class
|
38
|
+
@file_class ||= Class.new(Resources::File).tap { |dir| dir.client = self }
|
39
|
+
end
|
40
|
+
|
41
|
+
def fetcher
|
42
|
+
@fetcher ||= Fetcher.new(self)
|
43
|
+
end
|
44
|
+
|
45
|
+
def directory_path(path = '')
|
46
|
+
Pathname.new('directories').join(path)
|
47
|
+
end
|
48
|
+
|
49
|
+
def file_path(path = '')
|
50
|
+
Pathname.new('files').join(path)
|
51
|
+
end
|
52
|
+
|
53
|
+
def download_path(path = '')
|
54
|
+
Pathname.new('download').join(path)
|
55
|
+
end
|
56
|
+
|
57
|
+
def tree_path(path = '')
|
58
|
+
Pathname.new('tree').join(path)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative 'lib/versacommerce/theme_api_client/version'
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = 'versacommerce-theme_api_client'
|
5
|
+
spec.version = Versacommerce::ThemeAPIClient::VERSION
|
6
|
+
spec.authors = ['Tobias Bühlmann']
|
7
|
+
spec.email = ['buehlmann@versacommerce.de']
|
8
|
+
|
9
|
+
spec.summary = 'API Client for the VersaCommercer Theme API.'
|
10
|
+
spec.description = 'This Gem acts as an API Client for the VersaCommerce Theme API.'
|
11
|
+
spec.homepage = 'https://github.com/versacommerce/versacommerce-theme_api_client'
|
12
|
+
spec.license = 'MIT'
|
13
|
+
|
14
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
15
|
+
spec.bindir = 'exe'
|
16
|
+
spec.executables = spec.files.grep(/\Aexe/) { |f| File.basename(f) }
|
17
|
+
spec.require_paths = ['lib']
|
18
|
+
|
19
|
+
spec.required_ruby_version = '>= 2.0.0'
|
20
|
+
|
21
|
+
spec.add_runtime_dependency 'http', '0.7.2'
|
22
|
+
spec.add_runtime_dependency 'activesupport', '~> 4.2'
|
23
|
+
spec.add_runtime_dependency 'activemodel', '~> 4.2'
|
24
|
+
|
25
|
+
spec.add_development_dependency 'bundler', '~> 1.8'
|
26
|
+
spec.add_development_dependency 'rake', '~> 10.4'
|
27
|
+
spec.add_development_dependency 'pry', '0.10.1'
|
28
|
+
spec.add_development_dependency 'pry-stack_explorer', '0.4.9.2'
|
29
|
+
end
|
metadata
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: versacommerce-theme_api_client
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tobias Bühlmann
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-03-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: http
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.7.2
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.7.2
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activesupport
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '4.2'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '4.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: activemodel
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '4.2'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '4.2'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.8'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.8'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '10.4'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '10.4'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: pry
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 0.10.1
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 0.10.1
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: pry-stack_explorer
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 0.4.9.2
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 0.4.9.2
|
111
|
+
description: This Gem acts as an API Client for the VersaCommerce Theme API.
|
112
|
+
email:
|
113
|
+
- buehlmann@versacommerce.de
|
114
|
+
executables: []
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- ".gitignore"
|
119
|
+
- Gemfile
|
120
|
+
- LICENSE.txt
|
121
|
+
- README.md
|
122
|
+
- Rakefile
|
123
|
+
- bin/console
|
124
|
+
- bin/setup
|
125
|
+
- lib/versacommerce/theme_api_client.rb
|
126
|
+
- lib/versacommerce/theme_api_client/client.rb
|
127
|
+
- lib/versacommerce/theme_api_client/fetcher.rb
|
128
|
+
- lib/versacommerce/theme_api_client/relation.rb
|
129
|
+
- lib/versacommerce/theme_api_client/relations/directory_relation.rb
|
130
|
+
- lib/versacommerce/theme_api_client/relations/file_relation.rb
|
131
|
+
- lib/versacommerce/theme_api_client/resource.rb
|
132
|
+
- lib/versacommerce/theme_api_client/resources/directory.rb
|
133
|
+
- lib/versacommerce/theme_api_client/resources/file.rb
|
134
|
+
- lib/versacommerce/theme_api_client/resources/file_behaviour.rb
|
135
|
+
- lib/versacommerce/theme_api_client/version.rb
|
136
|
+
- versacommerce-theme_api_client.gemspec
|
137
|
+
homepage: https://github.com/versacommerce/versacommerce-theme_api_client
|
138
|
+
licenses:
|
139
|
+
- MIT
|
140
|
+
metadata: {}
|
141
|
+
post_install_message:
|
142
|
+
rdoc_options: []
|
143
|
+
require_paths:
|
144
|
+
- lib
|
145
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
146
|
+
requirements:
|
147
|
+
- - ">="
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: 2.0.0
|
150
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
151
|
+
requirements:
|
152
|
+
- - ">="
|
153
|
+
- !ruby/object:Gem::Version
|
154
|
+
version: '0'
|
155
|
+
requirements: []
|
156
|
+
rubyforge_project:
|
157
|
+
rubygems_version: 2.4.5
|
158
|
+
signing_key:
|
159
|
+
specification_version: 4
|
160
|
+
summary: API Client for the VersaCommercer Theme API.
|
161
|
+
test_files: []
|