weeblycloud 1.0.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,28 @@
1
+ require "weeblycloud/cloudresource"
2
+ require "weeblycloud/saveable"
3
+ require "weeblycloud/deleteable"
4
+
5
+ module Weeblycloud
6
+
7
+ # Represents a BlogPost resource.
8
+ # https://cloud-developer.weebly.com/blog-post.html
9
+ class BlogPost < CloudResource
10
+ include Deleteable, Saveable
11
+
12
+ def initialize(user_id, site_id, blog_id, blog_post_id, data = nil)
13
+ @user_id = user_id.to_i
14
+ @site_id = site_id.to_i
15
+ @blog_id = blog_id.to_i
16
+ @blog_post_id = blog_post_id.to_i
17
+ @endpoint = "user/#{@user_id}/site/#{@site_id}/blog/#{@blog_id}/post/#{@blog_post_id}"
18
+
19
+ super(data)
20
+ end
21
+
22
+ # Returns the blog_post_id
23
+ def id
24
+ @blog_post_id
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,119 @@
1
+ require "openssl"
2
+ require "base64"
3
+ require "json"
4
+ require "http"
5
+ require "weeblycloud/cloudclient/exceptions"
6
+
7
+ module Weeblycloud
8
+ class CloudClient
9
+ @@api_key = nil
10
+ @@api_secret = nil
11
+
12
+ def initialize(api_key = nil, api_secret = nil)
13
+ if api_key || api_secret
14
+ self.configure(api_key, api_secret)
15
+ elsif @@api_key.nil? || @@api_secret.nil?
16
+ raise "No API keys provided."
17
+ end
18
+
19
+ @BASE_API = "https://api.weeblycloud.com/"
20
+ end
21
+
22
+ # Globally configure API key and secret
23
+ def self.configure(api_key, api_secret)
24
+ @@api_key = api_key
25
+ @@api_secret = api_secret
26
+ end
27
+
28
+ # Make a GET request
29
+ def get(endpoint, options={})
30
+ ops = {
31
+ :page_size => nil,
32
+ :page => nil,
33
+ :params => {},
34
+ :content => {}
35
+ }
36
+ ops.merge!(options)
37
+ if ops[:page_size]
38
+ ops[:params].merge!({"page_size" => ops[:page_size]})
39
+ end
40
+
41
+ if ops[:page]
42
+ ops[:params].merge!({"page" => options[:page]})
43
+ end
44
+
45
+ return call("GET", endpoint, ops[:content], ops[:params])
46
+ end
47
+
48
+ # Make a POST request
49
+ def post(endpoint, options={})
50
+ ops = {:params => {}, :content => {}}
51
+ ops.merge!(options)
52
+
53
+ return call("POST", endpoint, ops[:content], ops[:params])
54
+ end
55
+
56
+ # Make a PATCH request
57
+ def patch(endpoint, options={})
58
+ ops = {:params => {}, :content => {}}
59
+ ops.merge!(options)
60
+
61
+ return call("PATCH", endpoint, ops[:content], ops[:params])
62
+ end
63
+
64
+ # Make a PUT request
65
+ def put(endpoint, options={})
66
+ ops = {:params => {}, :content => {}}
67
+ ops.merge!(options)
68
+
69
+ return call("PUT", endpoint, ops[:content], ops[:params])
70
+ end
71
+
72
+ # Make a DELETE request
73
+ def delete(endpoint, options={})
74
+ ops = {:params => {}, :content => {}}
75
+ ops.merge!(options)
76
+
77
+ return call("DELETE", endpoint, ops[:content], ops[:params])
78
+ end
79
+
80
+ private
81
+
82
+ # Make an API call
83
+ def call(method, endpoint, content={}, params={})
84
+ json_data = content.to_json
85
+ strip_slashes(endpoint)
86
+ headers = {
87
+ "Content-Type" => "application/json",
88
+ "X-Cloud-Client-Type" => "ruby",
89
+ "X-Cloud-Client-Version" => VERSION,
90
+ "X-Public-Key" => @@api_key,
91
+ "X-Signed-Request-Hash" => sign(method, endpoint, json_data)
92
+ }
93
+ url = @BASE_API + endpoint
94
+ response = HTTP.headers(headers).request(method, url, :body => json_data, :params => params)
95
+ return WeeblyCloudResponse.new(response, url, headers, content, params)
96
+ end
97
+
98
+ # Signs a request and returns the HMAC hash.
99
+ # See https://cloud-developer.weebly.com/about-the-rest-apis.html#signing-and-authenticating-requests
100
+ def sign(request_type, endpoint, content)
101
+ request = request_type + "\n" + endpoint + "\n" + content
102
+ digest = OpenSSL::Digest.new("sha256")
103
+ mac = OpenSSL::HMAC.hexdigest(digest, @@api_secret, request)
104
+ base = Base64.strict_encode64(mac)
105
+ return base
106
+ end
107
+
108
+ # Remove a "/" if it is the first or last character in a string.
109
+ def strip_slashes(str)
110
+ if str.index("/") === 0
111
+ str = str.slice(1..-1)
112
+ end
113
+ if str.index("/") === str.length
114
+ str = str.slice(0..-2)
115
+ end
116
+ end
117
+
118
+ end
119
+ end
@@ -0,0 +1,17 @@
1
+ module Weeblycloud
2
+
3
+ # Raised with a response error from the API
4
+ class ResponseError < StandardError
5
+ attr_reader :code, :message
6
+
7
+ def initialize(msg = "Unknown error occured", code)
8
+ @code = code
9
+ @message = msg
10
+ m = "(CODE: \##{@code}) #{@msg}"
11
+ super(m)
12
+ end
13
+ end
14
+
15
+ # Raised with invalid pagination method invocation
16
+ PaginationError = Class.new(StandardError)
17
+ end
@@ -0,0 +1,141 @@
1
+ require "HTTP"
2
+ require "json"
3
+ require "weeblycloud/cloudclient/exceptions"
4
+
5
+ module Weeblycloud
6
+
7
+ class WeeblyCloudResponse
8
+ include Enumerable
9
+ attr_reader :json, :page_size, :max_page, :current_page, :records, :list
10
+
11
+ def initialize(response, endpoint, headers, content, params)
12
+ @page_size = nil
13
+ @max_page = nil
14
+ @current_page = nil
15
+ @records = nil
16
+ @is_paginated = false
17
+
18
+ # private
19
+ @response = response
20
+ @endpoint = endpoint
21
+ @content = content
22
+ @params = params
23
+ @headers = headers
24
+ @first_iter = true
25
+
26
+ refresh()
27
+ end
28
+
29
+ # Returns whether the results are paginated
30
+ def paginated?
31
+ @is_paginated
32
+ end
33
+
34
+ # Get the next page, return True on success, False on failure. If the WeeblyCloudResponse is
35
+ # not paginated, raise an exception.
36
+ def next_page()
37
+ if not @is_paginated
38
+ raise PaginationError, "Not paginated"
39
+ end
40
+
41
+ if @current_page >= @max_page
42
+ return False
43
+ end
44
+
45
+ next_page = @params["page"].nil? ? 2 : @params["page"] + 1
46
+
47
+ @params.merge!({"page"=>next_page})
48
+ @response = HTTP.headers(@headers).get(@endpoint, :body => "{}", :params => @params)
49
+ refresh()
50
+ end
51
+
52
+ # Iterate over all pages. Accepts a block.
53
+ def each(&block)
54
+ if @is_paginated
55
+ # loop over all pages
56
+ while @current_page < @max_page
57
+ @list.each{|item| yield(item) }
58
+ if @first_iter
59
+ @first_iter = false
60
+ else
61
+ next_page()
62
+ end
63
+ end
64
+ else
65
+ # If it isn't paginated just do it once
66
+ @list.each{ |item| yield(item) }
67
+ end
68
+ end
69
+
70
+ # Returns the current page as a JSON string
71
+ def to_s()
72
+ @json.to_json
73
+ end
74
+
75
+ # Returns the current page as a hash
76
+ def to_hash()
77
+ @json
78
+ end
79
+
80
+ private
81
+
82
+ # Refreshes the internal parameters based on the current value of @request.
83
+ def refresh()
84
+ @status_code = @response.code
85
+
86
+ # Handle errors. Sometimes there may not be a response body (which is why ValueError)
87
+ # must be caught.
88
+ begin
89
+ if !([200,204].include? @status_code) || @response.parse().include?("error")
90
+ error = @response.parse()
91
+ raise ResponseError.new(error["error"]["message"], error["error"]["code"])
92
+ end
93
+ rescue NoMethodError, HTTP::Error
94
+ # Sometimes DELETE returns nothing. When this is the case, it will have a status code 204
95
+ raise ResponseError.new(code=@status_code) unless @status_code == 204
96
+ end
97
+
98
+ # Get information on paging if response is paginated
99
+ headers = @response.headers
100
+ if headers["X-Resultset-Total"] && headers["X-Resultset-Total"].to_i > headers["X-Resultset-Limit"].to_i
101
+ @is_paginated = true
102
+ @records = headers["X-Resultset-Total"].to_i
103
+ @page_size = headers["X-Resultset-Limit"].to_i
104
+ @current_page = headers["X-Resultset-Page"].to_i
105
+ @max_page = (@records.to_f / @page_size.to_i).ceil
106
+ end
107
+
108
+ # Save the content of the request
109
+ begin
110
+ @json = @response.parse()
111
+
112
+ # Parse the result so it can be paged over.
113
+ if @is_paginated
114
+ if @json.is_a?(Hash) == true
115
+ @list = @json.first[1]
116
+ else
117
+ @list = @json
118
+ end
119
+ else
120
+ if @json.kind_of?(Array)
121
+ @list = @json
122
+ elsif @json.values[0].kind_of?(Array)
123
+ @list = @json.values[0]
124
+ else
125
+ # I think this accounts for plan
126
+ @list = @json.values
127
+ end
128
+ end
129
+
130
+ rescue NoMethodError, HTTP::Error
131
+ # Sometimes DELETE returns nothing. When this is the case, it will have a status code 204
132
+ if @status_code == 204
133
+ @json = {"success" => true}
134
+ else
135
+ raise ResponseError.new(code = @status_code)
136
+ end
137
+ end
138
+ end
139
+
140
+ end
141
+ end
@@ -0,0 +1,62 @@
1
+ require 'weeblycloud/cloudclient/cloudclient'
2
+ require 'json'
3
+
4
+ module Weeblycloud
5
+
6
+ # A base resource that all other resources inherit from.
7
+ class CloudResource
8
+ attr_reader :properties
9
+
10
+ def initialize(data = nil)
11
+ @client = CloudClient.new
12
+ @properties = {}
13
+ @changed = {}
14
+
15
+ # If data isn't provided, make an API call to get it
16
+ if data
17
+ @properties = data
18
+ @got = true
19
+ else
20
+ get()
21
+ @got = true
22
+ end
23
+ end
24
+
25
+ # Get a property. Returns nil if the property does not exist.
26
+ def get_property(prop)
27
+ begin
28
+ return @properties.fetch(prop)
29
+ raise KeyError
30
+ if @got
31
+ return nil
32
+ else
33
+ get()
34
+ @got = true
35
+ return get_property(prop)
36
+ end
37
+ end
38
+ end
39
+
40
+ # Get a property. Returns nil if the property does not exist.
41
+ def [](prop)
42
+ get_property(prop)
43
+ end
44
+
45
+ # Returns the properties as a json string
46
+ def to_s
47
+ @properties.to_json
48
+ end
49
+
50
+ # Returns the ID for the resource object
51
+ def id
52
+ raise "Method not implemented."
53
+ end
54
+
55
+ # Gets the resources with an API call
56
+ def get
57
+ @response = @client.get(@endpoint)
58
+ @properties = @response.json
59
+ end
60
+
61
+ end
62
+ end
@@ -0,0 +1,13 @@
1
+ module Weeblycloud
2
+
3
+ # CloudResource objects may use this module if they can be deleted
4
+ module Deleteable
5
+
6
+ # Delete the resource
7
+ def delete
8
+ response = @client.delete(@endpoint)
9
+ return response.json["success"]
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,39 @@
1
+ require "weeblycloud/cloudresource"
2
+
3
+ require "weeblycloud/formentry"
4
+
5
+ module Weeblycloud
6
+
7
+ # Represents a Form resource.
8
+ # https://cloud-developer.weebly.com/form.html
9
+ class Form < CloudResource
10
+
11
+ def initialize(user_id, site_id, form_id, data = nil)
12
+ @user_id = user_id.to_i
13
+ @site_id = site_id.to_i
14
+ @form_id = form_id.to_i
15
+
16
+ @endpoint = "user/#{@user_id}/site/#{@site_id}/form/#{@form_id}"
17
+
18
+ super(data)
19
+ end
20
+
21
+ # Returns the form_id
22
+ def id
23
+ @form_id
24
+ end
25
+
26
+ # Returns a list of FormEntry resources for a given form subject to filters.
27
+ def list_form_entries(filters={})
28
+ result = @client.get(@endpoint + "/entry", :params=>filters)
29
+ return result.map { |i| FormEntry.new(@user_id, @site_id, i["form_entry_id"], i) }
30
+ end
31
+
32
+ # Return the FormEntry with the given id.
33
+ def get_form_entry(form_entry_id)
34
+ return FormEntry.new(@user_id, @site_id, @form_id, @form_entry_id)
35
+ end
36
+
37
+ end
38
+
39
+ end
@@ -0,0 +1,26 @@
1
+ require "weeblycloud/cloudresource"
2
+
3
+ module Weeblycloud
4
+
5
+ # Represents a FormEntry resource.
6
+ # https://cloud-developer.weebly.com/form-entry.html
7
+ class FormEntry < CloudResource
8
+
9
+ def initialize(user_id, site_id, form_id, form_entry_id, data = nil)
10
+ @user_id = user_id.to_i
11
+ @site_id = site_id.to_i
12
+ @form_id = form_id.to_i
13
+ @form_entry_id = form_entry_id.to_i
14
+ @endpoint = "user/#{@user_id}/site/#{@site_id}/form/#{@form_id}/entry/#{@form_entry_id}"
15
+
16
+ super(data)
17
+ end
18
+
19
+ # Returns the form_entry_id
20
+ def id
21
+ @form_entry_id
22
+ end
23
+
24
+ end
25
+
26
+ end