govdelivery-tms 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +15 -0
  2. data/Gemfile +14 -0
  3. data/README.md +324 -0
  4. data/Rakefile +20 -0
  5. data/govdelivery-tms.gemspec +30 -0
  6. data/lib/govdelivery-tms.rb +43 -0
  7. data/lib/govdelivery-tms/base.rb +37 -0
  8. data/lib/govdelivery-tms/client.rb +97 -0
  9. data/lib/govdelivery-tms/collection_resource.rb +54 -0
  10. data/lib/govdelivery-tms/connection.rb +34 -0
  11. data/lib/govdelivery-tms/errors.rb +58 -0
  12. data/lib/govdelivery-tms/instance_resource.rb +219 -0
  13. data/lib/govdelivery-tms/link_header.rb +223 -0
  14. data/lib/govdelivery-tms/logger.rb +36 -0
  15. data/lib/govdelivery-tms/mail/delivery_method.rb +63 -0
  16. data/lib/govdelivery-tms/resource/collections.rb +98 -0
  17. data/lib/govdelivery-tms/resource/command.rb +25 -0
  18. data/lib/govdelivery-tms/resource/command_action.rb +17 -0
  19. data/lib/govdelivery-tms/resource/command_type.rb +20 -0
  20. data/lib/govdelivery-tms/resource/email_message.rb +81 -0
  21. data/lib/govdelivery-tms/resource/email_recipient.rb +31 -0
  22. data/lib/govdelivery-tms/resource/email_recipient_click.rb +8 -0
  23. data/lib/govdelivery-tms/resource/email_recipient_open.rb +8 -0
  24. data/lib/govdelivery-tms/resource/email_template.rb +15 -0
  25. data/lib/govdelivery-tms/resource/from_address.rb +10 -0
  26. data/lib/govdelivery-tms/resource/inbound_sms_message.rb +8 -0
  27. data/lib/govdelivery-tms/resource/ipaws_acknowledgement.rb +9 -0
  28. data/lib/govdelivery-tms/resource/ipaws_alert.rb +38 -0
  29. data/lib/govdelivery-tms/resource/ipaws_category.rb +7 -0
  30. data/lib/govdelivery-tms/resource/ipaws_cog_profile.rb +29 -0
  31. data/lib/govdelivery-tms/resource/ipaws_event_code.rb +7 -0
  32. data/lib/govdelivery-tms/resource/ipaws_nwem_area.rb +18 -0
  33. data/lib/govdelivery-tms/resource/ipaws_nwem_authorization.rb +9 -0
  34. data/lib/govdelivery-tms/resource/ipaws_nwem_auxilary_data.rb +8 -0
  35. data/lib/govdelivery-tms/resource/ipaws_response_type.rb +7 -0
  36. data/lib/govdelivery-tms/resource/ipaws_static_resource.rb +8 -0
  37. data/lib/govdelivery-tms/resource/keyword.rb +30 -0
  38. data/lib/govdelivery-tms/resource/recipient.rb +10 -0
  39. data/lib/govdelivery-tms/resource/sms_message.rb +35 -0
  40. data/lib/govdelivery-tms/resource/webhook.rb +20 -0
  41. data/lib/govdelivery-tms/util/core_ext.rb +27 -0
  42. data/lib/govdelivery-tms/util/hal_link_parser.rb +50 -0
  43. data/lib/govdelivery-tms/version.rb +3 -0
  44. data/spec/client_spec.rb +41 -0
  45. data/spec/command_types_spec.rb +29 -0
  46. data/spec/email_message_spec.rb +102 -0
  47. data/spec/email_template_spec.rb +149 -0
  48. data/spec/errors_spec.rb +13 -0
  49. data/spec/from_address_spec.rb +86 -0
  50. data/spec/inbound_sms_messages_spec.rb +19 -0
  51. data/spec/instance_resource_spec.rb +61 -0
  52. data/spec/ipaws_acknowledgement_spec.rb +16 -0
  53. data/spec/ipaws_alerts_spec.rb +192 -0
  54. data/spec/ipaws_cog_profile_spec.rb +75 -0
  55. data/spec/ipaws_event_codes_spec.rb +35 -0
  56. data/spec/ipaws_nwem_areas_spec.rb +58 -0
  57. data/spec/ipaws_nwem_authorization_spec.rb +16 -0
  58. data/spec/keyword_spec.rb +62 -0
  59. data/spec/keywords_spec.rb +21 -0
  60. data/spec/mail/delivery_method_spec.rb +52 -0
  61. data/spec/sms_message_spec.rb +63 -0
  62. data/spec/sms_messages_spec.rb +21 -0
  63. data/spec/spec_helper.rb +31 -0
  64. data/spec/tms_spec.rb +7 -0
  65. metadata +172 -0
@@ -0,0 +1,37 @@
1
+ module TMS #:nodoc:
2
+ module Base
3
+ def self.included(base)
4
+ base.send(:include, TMS::Util::HalLinkParser)
5
+ base.extend(ClassMethods)
6
+ base.send(:include, InstanceMethods)
7
+ base.send(:include, TMS::CoreExt)
8
+ base.send(:extend, TMS::CoreExt)
9
+ end
10
+
11
+ attr_accessor :client, :href, :errors, :new_record
12
+
13
+ module ClassMethods
14
+ def to_param
15
+ tmsify(self)
16
+ end
17
+ end
18
+
19
+ module InstanceMethods
20
+ def initialize(client, href)
21
+ self.client = client
22
+ self.href = href
23
+ end
24
+
25
+ def new_record?
26
+ !!self.new_record
27
+ end
28
+
29
+ def href=(href)
30
+ self.new_record=false
31
+ @href=href
32
+ end
33
+ end
34
+
35
+ end
36
+
37
+ end
@@ -0,0 +1,97 @@
1
+ require 'logger'
2
+ # The client class to connect and talk to the TMS REST API.
3
+ class TMS::Client
4
+ include TMS::Util::HalLinkParser
5
+ include TMS::CoreExt
6
+
7
+ attr_accessor :connection, :href, :api_root, :logger
8
+
9
+ DEFAULTS = {:api_root => 'https://tms.govdelivery.com', :logger => nil}.freeze
10
+
11
+ # Create a new client and issue a request for the available resources for a given account.
12
+ #
13
+ # @param [String] auth_token The auth_token of your account
14
+ # @param [Hash] options
15
+ # @option options [String] :api_root The root URL of the TMS api. Defaults to localhost:3000
16
+ # @option options [Logger] :logger An instance of a Logger class (http transport information will be logged here) - defaults to nil
17
+ #
18
+ # @example
19
+ # client = TMS::Client.new("auth_token", {
20
+ # :api_root => "https://tms.govdelivery.com",
21
+ # :logger => Logger.new(STDOUT)})
22
+ # client = TMS::Client.new("auth_token", {
23
+ # api_root: "https://tms.govdelivery.com",
24
+ # logger: false})
25
+ def initialize(auth_token, options = DEFAULTS)
26
+ @api_root = options[:api_root]
27
+ @logger = options.fetch(:logger, setup_logging(options[:debug]))
28
+ connect!(auth_token, options.except(:api_root, :logger, :debug))
29
+ discover!
30
+ end
31
+
32
+ def connect!(auth_token, options={})
33
+ self.connection = TMS::Connection.new({:auth_token => auth_token, :api_root => api_root, :logger => logger}.merge!(options))
34
+ end
35
+
36
+ def discover!
37
+ services = get('/').body
38
+ parse_links(services['_links'])
39
+ end
40
+
41
+ def get(href)
42
+ response = raw_connection.get(href)
43
+ case response.status
44
+ when 500..599
45
+ raise TMS::Request::Error.new(response.status)
46
+ when 401..499
47
+ raise TMS::Request::Error.new(response.status)
48
+ when 202
49
+ raise TMS::Request::InProgress.new(response.body['message'])
50
+ else
51
+ return response
52
+ end
53
+ end
54
+
55
+ def post(obj)
56
+ raw_connection.post do |req|
57
+ req.url @api_root + obj.href
58
+ req.headers['Content-Type'] = 'application/json'
59
+ req.body = obj.to_json
60
+ end
61
+ end
62
+
63
+ def put(obj)
64
+ raw_connection.put do |req|
65
+ req.url @api_root + obj.href
66
+ req.headers['Content-Type'] = 'application/json'
67
+ req.body = obj.to_json
68
+ end
69
+ end
70
+
71
+ def delete(href)
72
+ response = raw_connection.delete(href)
73
+ case response.status
74
+ when 200...299
75
+ return response
76
+ else
77
+ raise TMS::Request::Error.new(response.status)
78
+ end
79
+ end
80
+
81
+ def raw_connection
82
+ connection.connection
83
+ end
84
+
85
+ def client
86
+ self
87
+ end
88
+
89
+ private
90
+
91
+ def setup_logging(debug)
92
+ logger = Logger.new(STDOUT)
93
+ logger.level = debug ? Logger::DEBUG : Logger::INFO
94
+ logger
95
+ end
96
+
97
+ end
@@ -0,0 +1,54 @@
1
+ module TMS::CollectionResource
2
+ def self.included(base)
3
+ base.send(:include, InstanceMethods)
4
+ end
5
+
6
+ module InstanceMethods
7
+ include TMS::Base
8
+ attr_accessor :collection
9
+
10
+ def initialize(client, href, items=nil)
11
+ super(client, href)
12
+ if items
13
+ initialize_collection_from_items(items)
14
+ else
15
+ self.collection = []
16
+ end
17
+
18
+ end
19
+
20
+ def get
21
+ response = client.get(href)
22
+ initialize_collection_from_items(response.body)
23
+ #setup page links from header
24
+ links = LinkHeader.parse(response.headers['link']).to_a.collect do |a|
25
+ {a[1][0].last => a[0]}
26
+ end
27
+ parse_links(links)
28
+ self
29
+ end
30
+
31
+ def build(attributes=nil)
32
+ thing = instance_class(self.class).new(client, self.href, attributes || {})
33
+ thing.new_record = true
34
+ self.collection << thing
35
+ thing
36
+ end
37
+
38
+ def to_json
39
+ @collection.map(&:to_json)
40
+ end
41
+
42
+ def to_s
43
+ "<#{self.class.inspect} href=#{self.href} collection=#{self.collection.inspect}>"
44
+ end
45
+
46
+ private
47
+
48
+ def initialize_collection_from_items(items)
49
+ self.collection = items.map do |attrs|
50
+ instance_class(self.class).new(client, nil, attrs)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,34 @@
1
+ class TMS::Connection
2
+ attr_accessor :auth_token, :api_root, :connection, :logger
3
+
4
+ def get(href)
5
+ resp = connection.get("#{href}.json")
6
+ if resp.status != 200
7
+ raise RecordNotFound.new("Could not find resource at #{href} (status #{resp.status})")
8
+ else
9
+ resp.body
10
+ end
11
+ end
12
+
13
+ def initialize(opts={})
14
+ self.auth_token = opts[:auth_token]
15
+ self.api_root = opts[:api_root]
16
+ self.logger = opts[:logger]
17
+ setup_connection
18
+ end
19
+
20
+ def setup_connection
21
+ self.connection = Faraday.new(:url => self.api_root) do |faraday|
22
+ faraday.use TMS::Logger, self.logger if self.logger
23
+ faraday.request :json
24
+ faraday.headers['X-AUTH-TOKEN'] = auth_token
25
+ faraday.headers[:user_agent] = "GovDelivery Ruby TMS::Client #{TMS::VERSION}"
26
+ faraday.response :json, :content_type => /\bjson$/
27
+ faraday.adapter :net_http
28
+ end
29
+ end
30
+
31
+ def dump_headers(headers)
32
+ headers.map { |k, v| "#{k}: #{v.inspect}" }.join("\n")
33
+ end
34
+ end
@@ -0,0 +1,58 @@
1
+ module TMS
2
+ module Request
3
+ # The generic TMS error class
4
+ class Error < StandardError
5
+ attr_reader :code
6
+
7
+ def initialize(code)
8
+ super("HTTP Error: #{code}")
9
+ @code=code
10
+ end
11
+ end
12
+
13
+ # Raised when a recipient list is still being constructed and a request is made to view the
14
+ # recipient list for a message.
15
+ class InProgress < StandardError;end
16
+ end
17
+
18
+ module Errors
19
+ class ServerError < StandardError
20
+ def initialize(response)
21
+ super("TMS client encountered a server error: #{response.status} \n#{response.body}")
22
+ end
23
+ end
24
+ class NoRelation < StandardError
25
+ def initialize(rel=nil, obj=nil)
26
+ message = "no link relation "
27
+ message << "'#{rel}' " if rel
28
+ message << 'is available'
29
+ message << " for #{obj}" if obj
30
+ super(message)
31
+ end
32
+ end
33
+ class InvalidVerb < StandardError
34
+ attr_reader :record
35
+
36
+ def initialize(record_or_string)
37
+ if record_or_string.respond_to?(:href)
38
+ @record = record_or_string
39
+ super("Couldn't POST #{record.class} to #{record.href}: #{record.errors.map { |k, v| "#{k} #{v.join(' and ')}" }.join(', ')}")
40
+ else
41
+ super(record_or_string)
42
+ end
43
+
44
+ end
45
+ end
46
+ class InvalidPost < InvalidVerb
47
+ end
48
+ class InvalidPut < InvalidVerb
49
+ end
50
+ class InvalidDelete < InvalidVerb
51
+ end
52
+ class InvalidGet < StandardError
53
+ def initialize(message=nil)
54
+ super(message || "Can't GET a resource after an invalid POST; either create a new object or fix errors")
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,219 @@
1
+ module TMS::InstanceResource
2
+ def self.included(base)
3
+ base.send(:include, TMS::Base)
4
+ base.extend(ClassMethods)
5
+ base.send(:include, InstanceMethods)
6
+ end
7
+
8
+ attr_accessor :response
9
+
10
+ module ClassMethods
11
+ ##
12
+ # Writeable attributes are sent on POST/PUT.
13
+ #
14
+ def writeable_attributes(*attrs)
15
+ @writeable_attributes ||= []
16
+ if attrs.any?
17
+ @writeable_attributes.map!(&:to_sym).concat(attrs).uniq! if attrs.any?
18
+ setup_attributes(@writeable_attributes, false)
19
+ end
20
+ @writeable_attributes
21
+ end
22
+
23
+ ##
24
+ # Linkable attributes are sent on POST/PUT.
25
+ #
26
+ def linkable_attributes(*attrs)
27
+ @linkable_attributes ||= []
28
+ if attrs.any?
29
+ @linkable_attributes.map!(&:to_sym).concat(attrs).uniq! if attrs.any?
30
+ end
31
+ @linkable_attributes
32
+ end
33
+
34
+ ##
35
+ # Readonly attributes don't get POSTed.
36
+ # (timestamps are included by default)
37
+ #
38
+ def readonly_attributes(*attrs)
39
+ @readonly_attributes ||= [:created_at, :updated_at, :completed_at]
40
+ if attrs.any?
41
+ @readonly_attributes.map!(&:to_sym).concat(attrs).uniq!
42
+ setup_attributes(@readonly_attributes, true)
43
+ end
44
+ @readonly_attributes
45
+ end
46
+
47
+ ##
48
+ # For collections that are represented as attributes (i.e. inline, no href)
49
+ #
50
+ # @example
51
+ # collection_attributes :recipients
52
+ #
53
+ def collection_attributes(*attrs)
54
+ @collection_attributes ||= []
55
+ if attrs.any?
56
+ @collection_attributes.map!(&:to_sym).concat(attrs).uniq!
57
+ @collection_attributes.each { |a| setup_collection(a) }
58
+ end
59
+ @collection_attributes
60
+ end
61
+
62
+ def custom_class_names
63
+ @custom_class_names ||= {}
64
+ end
65
+
66
+ ##
67
+ # For collections that are represented as attributes (i.e. inline, no href)
68
+ # and that have a class name other than the one we would infer.
69
+ #
70
+ # @example
71
+ # collection_attributes :recipients, 'EmailRecipient'
72
+ #
73
+ def collection_attribute(attr, tms_class)
74
+ @collection_attributes ||= []
75
+ @collection_attributes.push(attr).uniq!
76
+ setup_collection(attr, TMS.const_get(tms_class))
77
+ end
78
+
79
+ ##
80
+ # Read-only collection attributes don't get POSTed.
81
+ # Use this for collections that are represented as attributes, but cannot be modified.
82
+ #
83
+ # @example
84
+ # readonly_collection_attribute :opens
85
+ #
86
+ def readonly_collection_attribute(attr, tms_class)
87
+ @readonly_collection_attributes ||= []
88
+ @readonly_collection_attributes.push(attr).uniq!
89
+ setup_collection(attr, TMS.const_get(tms_class))
90
+ end
91
+
92
+ def setup_attributes(attrs, readonly=false)
93
+ attrs.map(&:to_sym).each do |property|
94
+ self.send :define_method, :"#{property}=", &lambda { |v| @attributes[property] = v } unless readonly
95
+ self.send :define_method, property.to_sym, &lambda { @attributes[property] }
96
+ end
97
+ end
98
+
99
+ def setup_collection(property, klass=nil)
100
+ if klass
101
+ custom_class_names[property] = klass
102
+ else
103
+ klass ||= TMS.const_get(property.to_s.capitalize)
104
+ end
105
+
106
+ self.send :define_method, property.to_sym, &lambda { @attributes[property] ||= klass.new(self.client, nil, nil) }
107
+ end
108
+ end
109
+
110
+ module InstanceMethods
111
+ attr_reader :links
112
+
113
+ def initialize(client, href=nil, attrs=nil)
114
+ super(client, href)
115
+ @attributes = {}
116
+ @links = {}
117
+ set_attributes_from_hash(attrs) if attrs
118
+ end
119
+
120
+ def attributes
121
+ @attributes
122
+ end
123
+
124
+ def get
125
+ raise TMS::Errors::InvalidGet if self.new_record?
126
+ process_response(client.get(self.href), :get) && self
127
+ end
128
+ alias_method :get!, :get
129
+
130
+ def post
131
+ self.errors = nil
132
+ process_response(client.post(self), :post)
133
+ end
134
+
135
+ def post!
136
+ self.post or raise TMS::Errors::InvalidPost.new(self)
137
+ end
138
+
139
+ def put
140
+ process_response(client.put(self), :put)
141
+ end
142
+
143
+ def put!
144
+ process_response(client.put(self), :put) or raise TMS::Errors::InvalidPut.new(self)
145
+ end
146
+
147
+ def delete
148
+ process_response(client.delete(self.href), :delete)
149
+ end
150
+
151
+ def delete!
152
+ process_response(client.delete(self.href), :delete) or raise TMS::Errors::InvalidDelete.new(self)
153
+ end
154
+
155
+ def to_s
156
+ "<#{self.class.inspect}#{' href=' + self.href if self.href} attributes=#{@attributes.inspect}>"
157
+ end
158
+
159
+ def to_json
160
+ json_hash = {}
161
+ self.class.writeable_attributes.each do |attr|
162
+ json_hash[attr] = self.send(attr)
163
+ end
164
+ self.class.collection_attributes.each do |coll|
165
+ json_hash[coll] = self.send(coll).to_json
166
+ end
167
+ self.class.linkable_attributes.each do |attr|
168
+ json_hash[:_links] ||= {}
169
+ json_hash[:_links][attr] = @links[attr]
170
+ end
171
+ json_hash
172
+ end
173
+
174
+ protected
175
+
176
+ def relation_class(rel)
177
+ self.class.custom_class_names[rel.to_sym] || super
178
+ end
179
+
180
+ def process_response(response, method)
181
+ self.response = response
182
+ error_class = TMS::Errors.const_get("Invalid#{method.to_s.capitalize}")
183
+ case response.status
184
+ when 204
185
+ return true
186
+ when 200..299
187
+ set_attributes_from_hash(response.body) if response.body.is_a?(Hash)
188
+ @links = {}
189
+ self.new_record=false
190
+ return true
191
+ when 401
192
+ raise error_class.new("401 Not Authorized")
193
+ when 404
194
+ raise(error_class.new("Can't POST to #{self.href}"))
195
+ when 500..599
196
+ raise(TMS::Errors::ServerError.new(response))
197
+ else # 422?
198
+ if response.body['errors']
199
+ self.errors = response.body['errors']
200
+ end
201
+ end
202
+ return false
203
+ end
204
+
205
+ def set_attributes_from_hash(hash)
206
+ hash.reject { |k, _| k=~/^_/ }.each do |property, value|
207
+ if self.class.collection_attributes.include?(property.to_sym)
208
+ klass = self.class.custom_class_names[property] || TMS.const_get(property.to_s.capitalize)
209
+ @attributes[property.to_sym] = klass.new(client, nil, value)
210
+ else
211
+ @attributes[property.to_sym] = value
212
+ end
213
+ end
214
+ self.errors = hash['errors']
215
+ parse_links(hash['_links'])
216
+ end
217
+
218
+ end
219
+ end