fjords-client 0.0.6

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.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ Gemfile.lock
2
+ pkg
3
+ .DS_Store
4
+ .ruby-version
5
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in fjords.gemspec
4
+ gemspec
5
+
6
+ group :development do
7
+ gem "rake"
8
+ gem "rspec"
9
+ gem "webmock"
10
+ end
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ require 'rspec/core/rake_task'
5
+ desc "Run RSpec"
6
+ RSpec::Core::RakeTask.new do |spec|
7
+ spec.pattern = 'spec/**/*_spec.rb'
8
+ spec.rspec_opts = ['--color', '--format nested']
9
+ end
10
+
11
+ desc "Run tests"
12
+ task :test => [:spec]
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/fjords-client/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Thomas Reynolds"]
6
+ gem.email = ["me@tdreyno.com"]
7
+ gem.description = %q{Fjords.cc API}
8
+ gem.summary = %q{Fjords.cc API}
9
+ gem.homepage = ""
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "fjords-client"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Fjords::Client::VERSION
17
+
18
+ gem.add_dependency("rest-client", "1.6.7")
19
+ gem.add_dependency("addressable", "2.3.2")
20
+ gem.add_dependency("stripe")
21
+ end
@@ -0,0 +1,391 @@
1
+ require 'json'
2
+ require 'rest_client'
3
+ require 'addressable/uri'
4
+ require 'tmpdir'
5
+ require 'digest/md5'
6
+ require 'pathname'
7
+
8
+ require "stripe"
9
+ Stripe.api_key = 'pk_vfcVMdOHZGAFgqLGSEtlSKzsXeBYI'
10
+
11
+ module Fjords
12
+ class Token
13
+ attr_reader :token
14
+
15
+ def initialize(token)
16
+ @token = token
17
+ end
18
+ end
19
+
20
+ class Client
21
+
22
+ EXCLUSION_GLOBS = ['..', '.', '*~', '#*#', '.*', '*/.*']
23
+
24
+ class << self
25
+ attr_accessor :host
26
+
27
+ def shared
28
+ @_shared_client ||= new
29
+ end
30
+
31
+ def host
32
+ @_host ||= "https://api.fjords.cc/v1"
33
+ end
34
+
35
+ def host=(host)
36
+ @_host = host
37
+ end
38
+ end
39
+
40
+ # Delegate methods
41
+ [
42
+ :login,
43
+ :logged_in?,
44
+ :push,
45
+ :prepare_push,
46
+ :sites,
47
+ :delete_site,
48
+ :signup,
49
+ :username_available?,
50
+ :get_stripe_token,
51
+ :logout,
52
+ :validate_domain,
53
+ :bugreport,
54
+ :info
55
+ ].each do |method|
56
+ module_eval(%Q{
57
+ def self.#{method}(*args, &block) # def self.login(*args, &block)
58
+ shared.__send__(:#{method}, *args, &block) # shared.__send__(:login, *args, &block)
59
+ end # end
60
+ })
61
+ end
62
+
63
+ class ClientError < RuntimeError
64
+ def initialize(e=nil)
65
+ super()
66
+
67
+ @e = e
68
+ end
69
+
70
+ def report_data
71
+ @e.backtrace
72
+ end
73
+ end
74
+ class Forbidden < ClientError; end
75
+ class ConnectionRefused < ClientError; end
76
+ class ResourceNotFound < ClientError; end
77
+ class LoginInvalid < ClientError; end
78
+ class NotAuthorizedError < ClientError; end
79
+ class InternalServerError < ClientError; end
80
+ class BadPathError < ClientError; end
81
+ class Conflict < ClientError; end
82
+ class UploadTooLarge < ClientError; end
83
+ class NothingToUpload < ClientError; end
84
+
85
+ attr_reader :username
86
+
87
+ def logged_in?
88
+ read_token
89
+ end
90
+
91
+ def signup(email, username, password, stripe_token)
92
+ json = post("/accounts", {
93
+ :email => email,
94
+ :username => username,
95
+ :password => password,
96
+ :stripe_token => stripe_token
97
+ })
98
+
99
+ token = json['token']
100
+
101
+ store_token(token)
102
+
103
+ Token.new(token)
104
+ end
105
+
106
+ def get_stripe_token(name_on_card, number, exp_month, exp_year, cvc)
107
+ Stripe::Token.create(
108
+ :card => {
109
+ :name => name_on_card,
110
+ :number => number.to_s,
111
+ :exp_month => exp_month.to_i,
112
+ :exp_year => exp_year.to_i,
113
+ :cvc => cvc.to_i
114
+ }
115
+ )
116
+ end
117
+
118
+ def login(username, password)
119
+ json = post("/login", { :username => username, :password => password })
120
+ token = json['token']
121
+ store_token(token)
122
+ Token.new(token)
123
+ end
124
+
125
+ def info
126
+ json = get("/info")
127
+ json
128
+ end
129
+
130
+ def logout
131
+ remove_token
132
+ end
133
+
134
+ def username_available?(username)
135
+ get("/users/#{username}")
136
+ false
137
+ rescue ResourceNotFound
138
+ true
139
+ end
140
+
141
+ def validate_domain(domain)
142
+ post("/site_available", {
143
+ :domain => domain
144
+ })
145
+ rescue Conflict
146
+ false
147
+ end
148
+
149
+ def delete_site(domain_name)
150
+ json = delete("/sites", :domain => domain_name)
151
+ !!json["site"]
152
+ end
153
+
154
+ def prepare_push(path, checksums=nil, domain_file="Fjordsfile")
155
+ raise BadPathError.new unless File.exists?(path) && File.directory?(path)
156
+
157
+ if checksums && checksums["site"] && checksums["site"]["existing_hashes"]
158
+ existing_hashes = checksums["site"]["existing_hashes"]
159
+ end
160
+
161
+ zipfile, changeless = zip!(path, domain_file, existing_hashes || nil)
162
+ upload_size = File.size(zipfile)
163
+
164
+ if upload_size > (1024*1024*50)
165
+ raise UploadTooLarge.new
166
+ end
167
+
168
+ if upload_size > 1024*1024
169
+ upload_size = (upload_size/(1024.0*1024.0)).ceil.to_s + 'M'
170
+ elsif upload_size > 0
171
+ upload_size = (upload_size/1024.0).ceil.to_s + 'K'
172
+ else
173
+ upload_size = '0K'
174
+ end
175
+
176
+ [zipfile, changeless, upload_size]
177
+ end
178
+
179
+ def push(zipfile, changeless=[], domain=nil, permanent=nil, &block)
180
+ raise BadPathError.new unless File.exists?(zipfile)
181
+
182
+ data = {
183
+ :site_data => File.new(zipfile, 'rb'),
184
+ :changeless => changeless
185
+ }
186
+ data[:domain] = domain if domain
187
+ data[:permanent] = permanent if permanent
188
+
189
+ json = post("/sites", data)
190
+
191
+ wait_until_complete(json['site'], &block)
192
+ end
193
+
194
+ def wait_until_complete(site, &block)
195
+ sleep(3)
196
+
197
+ json = get(site["url"])
198
+
199
+ block.call(json['site']) if block_given?
200
+
201
+ state = json['site']['state']
202
+
203
+ if state != "live"
204
+ wait_until_complete(site, &block)
205
+ else
206
+ json
207
+ end
208
+ end
209
+
210
+ def sites
211
+ json = get("/sites")
212
+ json['sites']
213
+ end
214
+
215
+ def bugreport(body, attach=nil)
216
+ params = { :body => body }
217
+ if attach
218
+ params[:attach] = File.new(attach, 'r')
219
+ end
220
+
221
+ json = post("/bugreport", params)
222
+ json['bugreport']
223
+ end
224
+
225
+ private
226
+
227
+ def get_file_paths(path)
228
+ Dir.glob("#{path}/**/*").reject do |f|
229
+ !File.file?(f) || EXCLUSION_GLOBS.any? do |e|
230
+ File.fnmatch(e, File.basename(f))
231
+ end
232
+ end
233
+ end
234
+
235
+ def zip!(path, domain_file, existing_hashes=nil)
236
+ excludes = EXCLUSION_GLOBS.dup
237
+ excludes << "#{domain_file}"
238
+ excludes << "*/#{domain_file}"
239
+ zipfile = File.expand_path(File.join(Dir.tmpdir, "pack-#{Time.now.to_i}.zip"))
240
+ changeless = []
241
+
242
+ if existing_hashes
243
+ all_files = get_file_paths(path)
244
+ unique_files = all_files.dup
245
+
246
+ all_files.each do |p|
247
+ relative_path = Pathname(p).relative_path_from(Pathname(path)).to_s
248
+ if relative_path == domain_file
249
+ unique_files.delete(p)
250
+ elsif existing_hashes[relative_path] == get_md5(p)
251
+ excludes << relative_path
252
+ changeless << relative_path
253
+ unique_files.delete(p)
254
+ end
255
+ end
256
+
257
+ if unique_files.length <= 0
258
+ raise NothingToUpload
259
+ end
260
+ end
261
+
262
+ zip_excludes = excludes.map { |e| "-x \"#{e}\"" }.join(' ')
263
+
264
+ Dir.chdir(path) do
265
+ puts `zip -y -q -r #{zipfile} . #{zip_excludes}`
266
+ end
267
+
268
+ [zipfile, changeless]
269
+ end
270
+
271
+ def require_login!
272
+ raise NotAuthorizedError unless @user || logged_in?
273
+ end
274
+
275
+ def path(resource_name)
276
+ "#{self.class.host}#{resource_name}"
277
+ end
278
+
279
+ def get_md5(path)
280
+ digest = ::Digest::MD5.new
281
+
282
+ ::File.open(path, 'rb') do |stream|
283
+ buffer = ""
284
+ digest.update(buffer) while stream.read(4096, buffer)
285
+ end
286
+
287
+ digest.to_s
288
+ end
289
+
290
+ def get(resource_path, extra_headers={})
291
+ request(:get, resource_path, nil, extra_headers)
292
+ end
293
+
294
+ def post(resource_path, params={}, extra_headers={})
295
+ request(:post, resource_path, params, extra_headers)
296
+ end
297
+
298
+ def delete(resource_path, params={}, extra_headers={})
299
+ request(:delete, resource_path, params, extra_headers)
300
+ end
301
+
302
+ def safe_url(url)
303
+ url = path(url)
304
+ Addressable::URI.parse(url).normalize.to_str
305
+ end
306
+
307
+ def request(type, url, params=nil, extra_headers={})
308
+ headers = {
309
+ :accept => :json
310
+ }
311
+
312
+ token = self.logged_in?
313
+ headers[:authorization] = %Q{Token token="#{token.token}"} if token
314
+
315
+ headers.merge!(extra_headers)
316
+
317
+ req = {
318
+ :method => type.to_sym,
319
+ :url => safe_url(url),
320
+ :headers => headers
321
+ }
322
+ req[:payload] = params unless params.nil?
323
+
324
+ begin
325
+ response = RestClient::Request.execute(req)
326
+ headers[:accept] == :json ? JSON.parse(response) : response
327
+ rescue RestClient::InternalServerError => e
328
+ raise InternalServerError.new(e)
329
+ rescue RestClient::ServerBrokeConnection => e
330
+ raise InternalServerError.new(e)
331
+ rescue RestClient::ResourceNotFound => e
332
+ raise ResourceNotFound.new(e)
333
+ rescue RestClient::Unauthorized => e
334
+ raise LoginInvalid.new(e)
335
+ rescue RestClient::Conflict => e
336
+ raise Conflict.new(e)
337
+ rescue RestClient::Forbidden => e
338
+ raise Forbidden.new(e)
339
+ rescue Errno::ECONNREFUSED => e
340
+ raise ConnectionRefused.new(e)
341
+ rescue Exception => e
342
+ raise ConnectionRefused.new(e)
343
+ end
344
+ end
345
+
346
+ TOKEN_FILE_PATH = "~/.fjords_token"
347
+
348
+ def read_token
349
+ return @token if @token
350
+
351
+ token_file = File.expand_path(TOKEN_FILE_PATH)
352
+
353
+ return nil unless File.exists?(token_file)
354
+
355
+ @token = Token.new(read_file(token_file).strip)
356
+ end
357
+
358
+ def remove_token
359
+ path = File.expand_path(TOKEN_FILE_PATH)
360
+ if File.exists?(path)
361
+ FileUtils.rm(path)
362
+ end
363
+ end
364
+
365
+ def store_token(token)
366
+ token_file = File.expand_path(TOKEN_FILE_PATH)
367
+ write_file(token_file, token)
368
+ read_token
369
+ end
370
+
371
+ def read_file(file)
372
+ File.open(file, File::RDONLY) {|f|
373
+ f.flock(File::LOCK_EX)
374
+ contents = f.read
375
+ f.flock(File::LOCK_UN)
376
+ contents
377
+ }
378
+ end
379
+
380
+ def write_file(file, contents)
381
+ File.open(file, File::RDWR | File::CREAT, 0600) {|f|
382
+ f.flock(File::LOCK_EX)
383
+ f.rewind
384
+ f.puts contents
385
+ f.flush
386
+ f.truncate(f.pos)
387
+ f.flock(File::LOCK_UN)
388
+ }
389
+ end
390
+ end
391
+ end
@@ -0,0 +1,5 @@
1
+ module Fjords
2
+ class Client
3
+ VERSION = "0.0.6"
4
+ end
5
+ end
@@ -0,0 +1,4 @@
1
+ FJORDS_CLIENT_ROOT = File.join(File.expand_path(File.dirname(__FILE__)), 'fjords-client')
2
+
3
+ require "#{FJORDS_CLIENT_ROOT}/fjords-client"
4
+ require "#{FJORDS_CLIENT_ROOT}/version"
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+ require 'securerandom'
3
+
4
+ describe "Site listing" do
5
+ before(:each) do
6
+ stub_request(:post, "https://api.fjords.cc/v1/users/testy/login").
7
+ with(:body => {"password"=>"wrong"}).
8
+ to_return(:status => 401, :body => "")
9
+
10
+ stub_request(:post, "https://api.fjords.cc/v1/users/testy/login").
11
+ with(:body => {"password"=>""}).
12
+ to_return(:status => 401, :body => "")
13
+
14
+ stub_request(:post, "https://api.fjords.cc/v1/users/testy/login").
15
+ with(:body => {"password"=>"mcgee"}).
16
+ to_return(:status => 200, :body => {
17
+ :account => { :username => "testy" },
18
+ :token => "tokenkey"
19
+ }.to_json)
20
+ end
21
+
22
+ it "should be false when not logged in" do
23
+ Fjords::Client.logged_in?.should be_false
24
+ end
25
+
26
+ it "should catch login invalid error with missing password" do
27
+ expect {
28
+ Fjords::Client.login("testy", "")
29
+ }.to raise_error(Fjords::Client::LoginInvalid)
30
+ WebMock.should have_requested(:post, "https://api.fjords.cc/v1/users/testy/login").with({ :body =>{"password"=>""} })
31
+ end
32
+
33
+ it "should catch login invalid error with wrong password" do
34
+ expect {
35
+ Fjords::Client.login("testy", "wrong")
36
+ }.to raise_error(Fjords::Client::LoginInvalid)
37
+ WebMock.should have_requested(:post, "https://api.fjords.cc/v1/users/testy/login").with({ :body =>{"password"=>"wrong"} })
38
+ end
39
+
40
+ it "should be true when a token exists" do
41
+ Fjords::Client.login("testy", "mcgee")
42
+ WebMock.should have_requested(:post, "https://api.fjords.cc/v1/users/testy/login").with({ :body =>{"password"=>"mcgee"} })
43
+
44
+ Fjords::Client.logged_in?.should be_true
45
+ end
46
+ end
@@ -0,0 +1,16 @@
1
+ require 'rspec'
2
+ require 'webmock/rspec'
3
+
4
+ $: << File.join(File.dirname(__FILE__), '..', 'lib', 'fjords-client')
5
+
6
+ require 'fjords-client'
7
+
8
+ RSpec.configure do |conf|
9
+ conf.include WebMock::API
10
+ conf.include WebMock::Matchers
11
+
12
+ conf.before(:all) do
13
+ Fjords::Client.logout
14
+ # Fjords::Client.host = "https://fake.fjords.cc/v1"
15
+ end
16
+ end
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fjords-client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.6
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Thomas Reynolds
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-01-28 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rest-client
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - '='
20
+ - !ruby/object:Gem::Version
21
+ version: 1.6.7
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - '='
28
+ - !ruby/object:Gem::Version
29
+ version: 1.6.7
30
+ - !ruby/object:Gem::Dependency
31
+ name: addressable
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - '='
36
+ - !ruby/object:Gem::Version
37
+ version: 2.3.2
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - '='
44
+ - !ruby/object:Gem::Version
45
+ version: 2.3.2
46
+ - !ruby/object:Gem::Dependency
47
+ name: stripe
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: Fjords.cc API
63
+ email:
64
+ - me@tdreyno.com
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - .gitignore
70
+ - Gemfile
71
+ - Rakefile
72
+ - fjords-client.gemspec
73
+ - lib/fjords-client.rb
74
+ - lib/fjords-client/fjords-client.rb
75
+ - lib/fjords-client/version.rb
76
+ - spec/logged_in_spec.rb
77
+ - spec/spec_helper.rb
78
+ homepage: ''
79
+ licenses: []
80
+ post_install_message:
81
+ rdoc_options: []
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ segments:
91
+ - 0
92
+ hash: 1471701448195210841
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ segments:
100
+ - 0
101
+ hash: 1471701448195210841
102
+ requirements: []
103
+ rubyforge_project:
104
+ rubygems_version: 1.8.23
105
+ signing_key:
106
+ specification_version: 3
107
+ summary: Fjords.cc API
108
+ test_files:
109
+ - spec/logged_in_spec.rb
110
+ - spec/spec_helper.rb