netlify 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
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'test'
6
+ t.pattern = "test/*_test.rb"
7
+ end
@@ -0,0 +1,17 @@
1
+ require "json"
2
+ require "netlify/version"
3
+ require "netlify/client"
4
+ require "netlify/collection_proxy"
5
+ require "netlify/model"
6
+ require "netlify/sites"
7
+ require "netlify/forms"
8
+ require "netlify/submissions"
9
+ require "netlify/files"
10
+ require "netlify/snippets"
11
+ require "netlify/users"
12
+ require "netlify/deploys"
13
+ require "netlify/dns_zones"
14
+ require "netlify/access_tokens"
15
+ require "netlify/multipass"
16
+
17
+ module Netlify; end
@@ -0,0 +1,5 @@
1
+ module Netlify
2
+ class AccessToken < Model
3
+ fields :id, :access_token, :user_id, :created_at
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ require "Netlify/access_token"
2
+
3
+ module Netlify
4
+ class AccessTokens < CollectionProxy
5
+ path "/access_tokens"
6
+ end
7
+ end
@@ -0,0 +1,113 @@
1
+ require 'oauth2'
2
+
3
+ module Netlify
4
+ class Client
5
+ ENDPOINT = ENV['OAUTH_CLIENT_API_URL'] || 'https://api.netlify.com'
6
+ API_VERSION = "v1"
7
+ RETRIES = 3
8
+
9
+ class NetlifyError < StandardError; end
10
+ class NotFoundError < NetlifyError; end
11
+ class ConnectionError < NetlifyError; end
12
+ class InternalServerError < NetlifyError; end
13
+ class AuthenticationError < NetlifyError; end
14
+
15
+ attr_accessor :client_id, :client_secret, :oauth, :access_token, :endpoint
16
+
17
+ def initialize(options)
18
+ self.client_id = options[:client_id]
19
+ self.client_secret = options[:client_secret]
20
+ self.access_token = options[:access_token]
21
+ self.endpoint = options[:endpoint] || ENDPOINT
22
+ self.oauth = OAuth2::Client.new(client_id, client_secret, :site => endpoint, :connection_build => lambda {|f|
23
+ f.request :multipart
24
+ f.request :url_encoded
25
+ f.adapter :net_http
26
+ })
27
+ end
28
+
29
+ def authorize_url(options)
30
+ oauth.auth_code.authorize_url(options)
31
+ end
32
+
33
+ def authorize_from_code!(authorization_code, options)
34
+ @oauth_token = oauth.auth_code.get_token(authorization_code, options)
35
+ self.access_token = oauth_token.token
36
+ end
37
+
38
+ def authorize_from_credentials!
39
+ @oauth_token = oauth.client_credentials.get_token
40
+ self.access_token = oauth_token.token
41
+ end
42
+
43
+ def sites
44
+ Sites.new(self)
45
+ end
46
+
47
+ def forms
48
+ Forms.new(self)
49
+ end
50
+
51
+ def submissions
52
+ Submissions.new(self)
53
+ end
54
+
55
+ def users
56
+ Users.new(self)
57
+ end
58
+
59
+ def dns_zones
60
+ DnsZones.new(self)
61
+ end
62
+
63
+ def access_tokens
64
+ AccessTokens.new(self)
65
+ end
66
+
67
+ def request(verb, path, opts={}, &block)
68
+ retries = 0
69
+ begin
70
+ raise AuthenticationError, "Authorize with Netlify before making requests" unless oauth_token
71
+
72
+ oauth_token.request(verb, ::File.join("/api", API_VERSION, path), opts, &block)
73
+ rescue OAuth2::Error => e
74
+ case e.response.status
75
+ when 401
76
+ raise AuthenticationError, message_for(e, "Authentication Error")
77
+ when 404
78
+ raise NotFoundError, message_for(e, "Not Found")
79
+ when 500
80
+ if retry_request?(verb, e.response.status, retries)
81
+ retries += 1
82
+ retry
83
+ else
84
+ raise InternalServerError, message_for(e, "Internal Server Error")
85
+ end
86
+ else
87
+ raise NetlifyError, message_for(e, "OAuth2 Error")
88
+ end
89
+ rescue Faraday::Error::ConnectionFailed, Faraday::Error::TimeoutError => e
90
+ if retry_request?(verb, e.response && e.response.status, retries)
91
+ retries += 1
92
+ retry
93
+ else
94
+ raise ConnectionError, message_for(e, "Connection Error")
95
+ end
96
+ end
97
+ end
98
+
99
+ private
100
+ def retry_request?(http_verb, status_code, retries)
101
+ return false unless [:get, :put, :delete, :head].include?(http_verb.to_s.downcase.to_sym)
102
+ return retries < 3 unless status_code && status_code == 422
103
+ end
104
+
105
+ def oauth_token
106
+ @oauth_token ||= access_token && OAuth2::AccessToken.new(oauth, access_token)
107
+ end
108
+
109
+ def message_for(error, default)
110
+ error.message.strip == "" ? default : error.message
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,48 @@
1
+ module Netlify
2
+ class CollectionProxy
3
+ include Enumerable
4
+
5
+ attr_accessor :client, :prefix
6
+
7
+ def self.path(value = nil)
8
+ return @path unless value
9
+ @path = value
10
+ end
11
+
12
+ def self.model(value = nil)
13
+ @model ||= Netlify.const_get(to_s.split("::").last.sub(/s$/, ''))
14
+ end
15
+
16
+ def initialize(client, prefix = nil)
17
+ self.client = client
18
+ self.prefix = prefix
19
+ end
20
+
21
+ def all(options = {})
22
+ response = client.request(:get, path, {:params => options})
23
+ response.parsed.map {|attributes| model.new(client, attributes.merge(:prefix => prefix)) } if response.parsed
24
+ end
25
+
26
+ def each(&block)
27
+ all.each(&block)
28
+ end
29
+
30
+ def get(id)
31
+ response = client.request(:get, ::File.join(path, id))
32
+ model.new(client, response.parsed.merge(:prefix => prefix)) if response.parsed
33
+ end
34
+
35
+ def create(attributes)
36
+ response = client.request(:post, path, :body => attributes)
37
+ model.new(client, response.parsed.merge(:prefix => prefix)) if response.parsed
38
+ end
39
+
40
+ def model
41
+ self.class.model
42
+ end
43
+
44
+ def path
45
+ [prefix, self.class.path].compact.join("/")
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,58 @@
1
+ module Netlify
2
+ class Deploy < Model
3
+ fields :id, :state, :premium, :claimed, :name, :custom_domain, :url,
4
+ :admin_url, :deploy_url, :screenshot_url, :created_at, :updated_at,
5
+ :user_id, :required
6
+
7
+ def upload_dir(dir)
8
+ return unless state == "uploading"
9
+
10
+ shas = Hash.new { [] }
11
+ glob = ::File.join(dir, "**", "*")
12
+
13
+ Dir.glob(glob) do |file|
14
+ next unless ::File.file?(file)
15
+ pathname = ::File.join("/", file[dir.length..-1])
16
+ next if pathname.match(/(^\/?__MACOSX\/|\/\.)/)
17
+ sha = Digest::SHA1.hexdigest(::File.read(file))
18
+ shas[sha] = shas[sha] + [pathname]
19
+ end
20
+
21
+ (required || []).each do |sha|
22
+ shas[sha].each do |pathname|
23
+ client.request(:put, ::File.join(path, "files", URI.encode(pathname)), :body => ::File.read(::File.join(dir, pathname)), :headers => {"Content-Type" => "application/octet-stream"})
24
+ end
25
+ end
26
+
27
+ refresh
28
+ end
29
+
30
+ def wait_for_ready(timeout = 900)
31
+ start = Time.now
32
+ while !(ready?)
33
+ sleep 5
34
+ refresh
35
+ puts "Got state: #{state}"
36
+ raise "Error processing site: #{error_message}" if error?
37
+ yield(self) if block_given?
38
+ raise "Timeout while waiting for ready" if Time.now - start > timeout
39
+ end
40
+ self
41
+ end
42
+
43
+ def ready?
44
+ state == "ready"
45
+ end
46
+
47
+ def error?
48
+ state == "error"
49
+ end
50
+
51
+ def publish
52
+ response = client.request(:post, ::File.join(path, "restore"))
53
+ process(response.parsed)
54
+ self
55
+ end
56
+ alias :restore :publish
57
+ end
58
+ end
@@ -0,0 +1,43 @@
1
+ require "Netlify/deploy"
2
+
3
+ module Netlify
4
+ class Deploys < CollectionProxy
5
+ path "/deploys"
6
+
7
+ def create(attributes)
8
+ if attributes[:dir]
9
+ response = client.request(:post, path,
10
+ :body => JSON.generate({:files => inventory(attributes[:dir]), :draft => attributes[:draft] || false}),
11
+ :headers => {"Content-Type" => "application/json"}
12
+ )
13
+ Deploy.new(client, response.parsed).tap do |deploy|
14
+ deploy.upload_dir(attributes[:dir])
15
+ end
16
+ elsif attributes[:zip]
17
+ request_path = attributes[:draft] ? "#{path}?draft=true" : path
18
+ response = client.request(:post, request_path,
19
+ :body => ::File.read(attributes[:zip]),
20
+ :headers => {"Content-Type" => "application/zip"}
21
+ )
22
+ Deploy.new(client, response.parsed)
23
+ else
24
+ raise "Need dir or zip to create a deploy"
25
+ end
26
+ end
27
+
28
+ def draft(attributes)
29
+ create(attributes.merge(:draft => true))
30
+ end
31
+
32
+ private
33
+ def inventory(dir)
34
+ files = {}
35
+ Dir[::File.join(dir, "**", "*")].each do |file|
36
+ next unless ::File.file?(file)
37
+ path = ::File.join("/", file[dir.length..-1])
38
+ files[path] = Digest::SHA1.hexdigest(::File.read(file))
39
+ end
40
+ files
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,5 @@
1
+ module Netlify
2
+ class DnsRecord < Model
3
+ fields :id, :hostname, :type, :value, :ttl, :domain_id
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ require "Netlify/dns_record"
2
+
3
+ module Netlify
4
+ class DnsRecords < CollectionProxy
5
+ path "/dns_records"
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ require "Netlify/dns_records"
2
+
3
+ module Netlify
4
+ class DnsZone < Model
5
+ fields :id, :name, :user_id, :created_at, :updated_at
6
+
7
+ def dns_records
8
+ DnsRecords.new(client, path)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ require "Netlify/dns_zone"
2
+
3
+ module Netlify
4
+ class DnsZones < CollectionProxy
5
+ path "/dns_zones"
6
+
7
+ def dns_records
8
+ DnsRecords.new(client, path)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ module Netlify
2
+ class File < Model
3
+ fields :id, :path, :sha, :mime_type, :size
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ require "Netlify/file"
2
+
3
+ module Netlify
4
+ class Files < CollectionProxy
5
+ path "/files"
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ module Netlify
2
+ class Form < Model
3
+ fields :id, :site_id, :name, :paths, :submission_count, :fields, :created_at
4
+
5
+ def submissions
6
+ Submissions.new(client, path)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ require "Netlify/form"
2
+
3
+ module Netlify
4
+ class Forms < CollectionProxy
5
+ path "/forms"
6
+ end
7
+ end
@@ -0,0 +1,65 @@
1
+ module Netlify
2
+ class Model
3
+ attr_reader :client, :attributes, :prefix
4
+
5
+ def self.fields(*names)
6
+ return @fields if names.empty?
7
+
8
+ @fields ||= []
9
+
10
+ names.each do |name|
11
+ define_method name do
12
+ @attributes[name.to_sym]
13
+ end
14
+
15
+ define_method "#{name}=" do |value|
16
+ @attributes[name.to_sym] = value
17
+ end
18
+
19
+ @fields.push(name.to_sym)
20
+ end
21
+ end
22
+
23
+ def self.collection(value = nil)
24
+ @collection ||= Netlify.const_get(to_s.split("::").last + "s")
25
+ end
26
+
27
+ def initialize(client, attributes)
28
+ @client = client
29
+ @attributes = {}
30
+ @prefix = attributes.delete(:prefix)
31
+ process(attributes)
32
+ end
33
+
34
+ def process(attributes)
35
+ self.class.fields.each do |field|
36
+ if attributes.has_key?(field) || attributes.has_key?(field.to_s)
37
+ @attributes[field] = attributes[field] || attributes[field.to_s]
38
+ end
39
+ end
40
+ self
41
+ end
42
+
43
+ def update(attributes)
44
+ response = client.request(:put, path, :body => attributes)
45
+ process(response.parsed) if response.parsed
46
+ end
47
+
48
+ def destroy
49
+ client.request(:delete, path)
50
+ end
51
+
52
+ def refresh
53
+ response = client.request(:get, path)
54
+ process(response.parsed)
55
+ end
56
+
57
+ def collection
58
+ self.class.collection
59
+ end
60
+
61
+ def path
62
+ ::File.join(*[prefix, collection.path, id.to_s].compact)
63
+ end
64
+ end
65
+ end